Packet Analysis and Header Sniffers

by Javaman

I decided not too long ago that I wanted to gain a deeper understanding of how inter-networking functions on the lower levels, particularly the function and stateful interactions of protocols.  After studying several RFCs, writing some code, and asking many questions, I feel much more in touch with the raw data floating across my Cat 5 strands than I ever have before.  Hopefully this article and the code attached will help you make the same journey.

The reader may ask what he or she may gain from reading this article and examining the attached source.  I hoped to target several groups: the novice programmer who wants to learn a form of network coding, the sys/net admin looking to add a few more tools to their kit, and the beginning hacker interested in adding some network level skills to his or her capabilities.

This is a good time to mention that all code here is for educational use only.  It was never intended to be the basis of an attack, theoretical or not.  The source displayed was written to teach me several things, namely libpcap (the packet capture library used), low-level packet analysis, and possibly primitive Intrusion Detection System (IDS) techniques.  With that said, let's discuss some basic network then move to the header code.

Protocol Introduction

Almost everyone who is reading this article has heard the term TCP/IP, but all may not understand the significance of the pairing.  The name of the game when it comes to modern networking is encapsulation.  Like a digital matryoshka doll, data transported via TCP is wrapped with a TCP header, which is in turn wrapped by an IP header, which is in turn packaged in an Ethernet header.  Not only is this true for TCP, but for any protocol carried by IP, such as ICMP, UDP, and numerous routing protocols.  It may be a good idea to eventually commit the size of each header in bytes to memory, which can be determined by writing a simple program.  This can be extracted from the code attached.

Each protocol, which has its own associated header, serves a different task.  Additionally, each inner protocol adds new functionality.  For example, the Ethernet header provides the simplest of addressing (which card on the subnet to pass a packet to), while TCP governs things such as ordered and guaranteed packet delivery, along with multiplexing.  This provides future growth in our networks, and is probably the reason why machines that are 20 years old are still capable of communicating on the networks of today, and undreamed of protocols and transmission techniques of today can still work across the majority of the networks.

Because of the length involved and the reality that it would be impossible for me to improve upon the original RFCs, complete specifications on how each protocol works and finite state diagrams for connection-based techniques are not included.  This information can be pulled from the RFCs listed throughout the document.  If print is preferred, I highly recommend the books written by the late W. Richard Stevens.

General (and Amazingly Brief) Protocol Overview

IP over Ethernet:  The lowest layer that we shall be concerned with, the Ethernet frame, defines some basic properties about what our packet is going to look like.  The Ethernet header will define the source and destination Ethernet addresses, along with the ethertype, which can be thought of as the data stored inside the headers, be it an IP packet or an ARP request.  More information can be found in RFC #1042 and RFC #1700.

ARP over Ethernet:  ARP and RARP are two components used in transporting IP over Ethernet.  ARP, or Address Resolution Protocol, a facility to translate an IP to an Ethernet address.  This allows a machine to know which gateway to address a packet to if it is outbound of the subnet or which machine on the subnet the packet is destined for.  RARP, or Reverse Address Resolution Protocol, provides a complimentary service to ARP, and converts an Ethernet address too an IP.  Refer to RFC #826.

ICMP:  ICMP, or Internet Control Message Protocol, is where many functions pertaining to Internet operations, such as dealing with routing difficulties, resides.  Facilities such as ping (ICMP_ECHO) operate on the ICMP level.  All these protocols have a similar header, with some possessing additional fields, such as Timestamp/Timestamp Reply's three timestamp fields.  Consult RFC #792.

UDP/TCP:  Through the use of sockets, UDP and TCP allow for multiplexing of the communication between two machines.  Rather than every packet being destined for the IP only, User Datagram Protocol (UDP) and Transmission Control Protocol (TCP) allow for an additional address, known as a port.  UDP only facilitates this functionality, but TCP goes further.  The protocol allows for guaranteed and ordered delivery of data through the use of sequence numbers, a metric unique to the current packet used for identification and ordering, and acknowledgment numbers, which are passed from the receiver to the sender to inform the latter of what the last packet received was.  A separate field just containing flags indicating the negotiation and termination of communication is included additionally inside the TCP header.  All the fields and flags for TCP are too numerous to mention here.  Please read RFC #768 and RFC #793.

Functionality

By now, one may be wondering what the function of the code below is: rather than like most packet sniffers which grab the payload of the communication, this code displays the headers of the protocols only.  Why is this useful you may ask?  Well, it's simple: examining initial SYN counts, watching badly formed headers drop by, determining sources of attack, etc.  I wrote this tool to gain a better understanding of networking in general.  Hopefully it will assist you in the same way.

Explanation of Code

The comments in the code make the source rather self-explanatory.  The program does some variable initialization, command line parameter parsing, and then some libpcap calls to locate the network card.  Each packet is then passed to a function called handler(), which then takes the char array (the raw packet), and formats it into something a bit more readable.  This may seem oversimplified, but I believe the code contains the best explanation possible.  Learning to read source code is an important skill, and is the one by which I learned most of my programming capabilities from.

Keep in mind that libpcap, a cross-platform library, is required for this code.  Libpcap can be found at:

ftp://ftp.ee.lbl.gov/libpcap-0.4.tar.Z

www.tcpdump.org

To compile the code, enter the following command:

$ gcc -o headers headers.c -lpcap

This code has to be run as root, since it involves putting an interface into promiscuous mode.  As with anything that needs to be run as root, read all the source carefully beforehand.  This is just common sense.

If you are really really paranoid, you should be able to chmod your Ethernet device to "666", but I would not recommend that on a box with more than one user.


/*
 * headers.c, a header analysis tool written by Javaman
 * This software is for educational use only.
 * You have been warned.
 */

#include <pcap.h>
#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ether.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <netinet/tcp.h>
#include <netinet/ip_icmp.h>
#include <sys/socket.h>

char ethdump, ipdump, tcpdump, udpdump, icmpdump, arpdump;
char ethlen, iplen, tcplen, udplen, icmplen, arplen;

void handler(char *, const struct pcap_pkthdr *, const u_char *);
void help(void);

int main(int argc, char **argv)
{

  int buffsize = 65535;
  int promisc = 1;
  int timeout = 1000;

  char pcap_err[PCAP_ERRBUF_SIZE];
  u_char buffer[255];
  char i;
  char *dev;
  struct in_addr net, mask;
  pcap_t *pcap_nic;

  ethdump = 0;
  ipdump = 0;
  icmpdump = 0;
  tcpdump = 0;
  udpdump = 0;
  arpdump = 0;

  ethlen = sizeof(struct ether_header);
  iplen = sizeof(struct iphdr);
  tcplen = sizeof(struct tcphdr);
  udplen = sizeof(struct udphdr);
  icmplen = sizeof(struct icmphdr);
  arplen = sizeof(struct ether_arp);

  if (argc == 1) {
    help();
    exit(0);
  }

  while ((i = getopt(argc, argv, "eutica")) != EOF) {
    switch (i) {
      case 'i':
        ipdump = 1;
        break;

      case 'e':
        ethdump = 1;
        break;

      case 't':
        tcpdump = 1;
        break;

      case 'u':
        udpdump = 1;
        break;

      case 'c':
        icmpdump = 1;
        break;

      case 'a':
        arpdump = 1;
        break;
    }
  }

  if (!(dev = pcap_lookupdev(pcap_err))) {
    perror(pcap_err);
    exit(-1);
  }

  if ((pcap_nic =
       pcap_open_live(dev, buffsize, promisc, timeout, pcap_err)) == NULL) {
    perror(pcap_err);
    exit(-1);
  }

  if (pcap_lookupnet(dev, &net.s_addr, &mask.s_addr, pcap_err) == -1) {
    perror(pcap_err);
    exit(-1);
  }

  while (pcap_loop(pcap_nic, -1, (pcap_handler) handler, buffer));
}

void handler(char *usr, const struct pcap_pkthdr *header, const u_char * pkt)
{

  struct ether_header *ethheader;
  struct iphdr *ipheader;
  struct udphdr *udpheader;
  struct tcphdr *tcpheader;
  struct icmphdr *icmpheader;
  struct ether_arp *arppkt;
  struct in_addr source, dest;
  int y;

  ethheader = (struct ether_header *) pkt;

  if (ethdump) {
    printf("\nEthernet:\n");
    for (y = 0; y < 6; y++) {
      printf("%02x", ethheader->ether_dhost[y]);
      if (y != 5) {
        printf(":");
      } else {
        printf("\n");
      }
    }

    for (y = 0; y < 6; y++) {
      printf("%02x", ethheader->ether_shost[y]);
      if (y != 5) {
        printf(":");
      } else {
        printf("\n");
      }
    }
    printf("Proto: %04x\n", ntohs(ethheader->ether_type));
  }

  if (arpdump
      && ((ethheader->ether_type == 0x0608)
          || (ethheader->ether_type == 0x3508))) {
    arppkt = (struct ether_arp *) (pkt + ethlen);
    memcpy(&source, &arppkt->arp_spa, 4);
    memcpy(&dest, &arppkt->arp_tpa, 4);
    printf("\nARP:\n");
    printf("%04x %04x\n", ntohs(arppkt->ea_hdr.ar_hrd),
           ntohs(arppkt->ea_hdr.ar_pro));
    printf("%02x %02x %04x\n", arppkt->ea_hdr.ar_hln, arppkt->ea_hdr.ar_pln,
           ntohs(arppkt->ea_hdr.ar_op));
    for (y = 0; y < 6; y++) {
      printf("%02x", arppkt->arp_sha[y]);
      if (y != 5) {
        printf(":");
      } else {
        printf("\n");
      }
    }

    for (y = 0; y < 4; y++) {
      printf("%02x", arppkt->arp_spa[y]);
      if (y != 3) {
        printf(".");
      } else {
        printf("\t(%s)\n", inet_ntoa(source));
      }
    }

    for (y = 0; y < 6; y++) {
      printf("%02x", arppkt->arp_tha[y]);
      if (y != 5) {
        printf(":");
      } else {
        printf("\n");
      }
    }

    for (y = 0; y < 4; y++) {
      printf("%02x", arppkt->arp_tpa[y]);
      if (y != 3) {
        printf(".");
      } else {
        printf("\t(%s)\n", inet_ntoa(dest));
      }
    }
  }

  if (ethheader->ether_type == 0x0008) {
    ipheader = (struct iphdr *) (pkt + ethlen);

    if (ipdump && (ipheader->version == 0x04)) {
      memcpy(&source, &ipheader->saddr, 4);
      memcpy(&dest, &ipheader->daddr, 4);
      printf("\nIP:\n");
      printf("%1x %1x %02x %04x\n", ipheader->version, ipheader->ihl,
             ipheader->tos, ntohs(ipheader->tot_len));
      printf("%04x   %04x\n", ntohs(ipheader->id), ntohs(ipheader->frag_off));
      printf("%02x %02x  %04x\n", ipheader->ttl, ipheader->protocol,
             ntohs(ipheader->check));
      printf("%08x    (%s)\n", ntohl(ipheader->saddr), inet_ntoa(source));
      printf("%08x    (%s)\n", ntohl(ipheader->daddr), inet_ntoa(dest));
    }

    if (udpdump && (ipheader->protocol == 0x11)) {
      udpheader = (struct udphdr *) (pkt + ethlen + iplen);
      printf("\nUDP:\n");
      printf("%04x %04x\n", ntohs(udpheader->source), ntohs(udpheader->dest));
      printf("%04x %04x\n", ntohs(udpheader->len), ntohs(udpheader->check));
    }

    if (tcpdump && (ipheader->protocol == 0x06)) {
      tcpheader = (struct tcphdr *) (pkt + ethlen + iplen);
      printf("\nTCP:\n");
      printf("%04x %04x\n", ntohs(tcpheader->source), ntohs(tcpheader->dest));
      printf("%08x\n", ntohl(tcpheader->seq));
      printf("%08x\n", ntohl(tcpheader->ack_seq));
      printf("%1x %02x %1x:%1x:%1x:%1x:%1x:%1x %04x\n", tcpheader->doff,
             tcpheader->res1 + tcpheader->res2, tcpheader->urg, tcpheader->ack,
             tcpheader->psh, tcpheader->rst, tcpheader->syn, tcpheader->fin,
             ntohs(tcpheader->window));
      printf("%04x %04x\n", ntohs(tcpheader->check), ntohs(tcpheader->urg_ptr));
    }

    if (icmpdump && (ipheader->protocol == 0x01)) {
      icmpheader = (struct icmphdr *) (pkt + ethlen + iplen);
      printf("\nICMP:\n");
      printf("%02x %02x %04x\n", icmpheader->type, icmpheader->code,
             ntohs(icmpheader->checksum));
      if ((icmpheader->type == 0x08) || (icmpheader->type == 0x00)
          || (icmpheader->type == 0x0d) || (icmpheader->type == 0x0e)
          || (icmpheader->type == 0x0f) || (icmpheader->type == 0x10)) {
        printf("%04x %04x\n", ntohs(icmpheader->un.echo.id),
               ntohs(icmpheader->un.echo.sequence));
      } else if (icmpheader->type == 0x05) {
        printf("%08x  Gw: %s\n", ntohl(icmpheader->un.gateway),
               inet_ntoa(dest));
      }
    }
  }
  return;
}

void help(void)
{
  printf("Headers by Javaman v1\n");
  printf("For information purposes only.\n");
  printf("Options:\n");
  printf("\t-e\tDump Ethernet header\n");
  printf("\t-a\tDump ARP/RARP info\n");
  printf("\t-i\tDump IP header\n");
  printf("\t-c\tDump ICMP header\n");
  printf("\t-t\tDump TCP header\n");
  printf("\t-u\tDump UDP header\n");
}

Conclusion

Hopefully this tool has helped introduce the reader into some basic concepts of low-level IP operation.  This snippet can be added to to provide some basic IDS functionality, and possibly a tool for other, as of yet unimagined projects.

Pertinent Links

RFC Database: www.faqs.org/rfcs

This has been where I have been reading my RFCs from.  Libpcap: ftp.ee.lbl.gov/libpcap-0.4.tar.Z

The code was written using libpcap v0.4.  Hopefully by the time this article is published, the code will not be updated, just for simplicity purposes only.  Philtered.net: www.philtered.net

The code contained in this article can be downloaded from this site, with no comments, of course.  If you want commented code, buy this magazine.  Additionally, my e-mail address along with other projects from our group can be found.

RFC List

Code: headers.c

Return to $2600 Index