Packet Forging (Part 1.0) IP & UDP

c
packetforging
networking

#1

Hello there all, today we will be discussing packet forging.
There are some uses for packet forging including (D)DOS’ing, spoofing, MiTM attacks and way more that don’t come to mind at the moment.
While this may sound like a very difficult subject it is actually quite simple. Basicly it’s just copy and paste.
We will be using C on Linux. We will be covering IP, UDP and TCP, beginning with IP.

Before we will begin with IP however, let’s first try to make things less abstract.
I will mainly be talking in bits note that there are some terms you should know when reading stuff about this online:

  • Byte
    A byte is 8 bits, char's in C are 1 byte, also u_int8 is 1 byte.
  • Word
    A word is not a word as you find it in the dictionary, in CS a word often means 16 bit, or 2 byte, altough it is sometimes used for DWord, in C it is often called as u_int16
  • D(ouble) Word
    A Dword is 2 words, which is 4 bytes which is 32 bit. In C it would be called as u_int32 or int (Though the latter is not always 32 bit).
  • Q(uad) Word
    A Qword is 2 Dwords, or 4 words or 8 byte or 64 bit.

Let’s say we have an imaginary protocol (like IP, UDP TCP), which only has 4 fields:

  • 32 bits Source IP
  • 32 bits Destination IP
  • 16 bits Source Port
  • 16 bits Destination

Then in C we would use a struct (and not an array) for this, because the members have different sizes (32 bit and 16 bit),

Our header would look something like:

struct header {
  u_int32 src_ip;
  u_int32 dest_ip;
  u_int16 src_port;
  u_int16 dest_port;
};

As you can see there is no advanced wizardy involved.
We just look at what is all has and put it in there in the same sequence with the corresponding sizes.
So now let’s look at the IP header

IP Header

First let’s look at a schema for the IP header

And now let’s look at corresponding struct. It can be found in /usr/include/netinet/ip.h, check it out using:
cat /usr/include/netinet/ip.h | less

In here we will find:

struct iphdr
  {
#if __BYTE_ORDER == __LITTLE_ENDIAN
    unsigned int ihl:4;
    unsigned int version:4;
#elif __BYTE_ORDER == __BIG_ENDIAN
    unsigned int version:4;
    unsigned int ihl:4;
#else
# error "Please fix <bits/endian.h>"
#endif
    u_int8_t tos;
    u_int16_t tot_len;
    u_int16_t id;
    u_int16_t frag_off;
    u_int8_t ttl;
    u_int8_t protocol;
    u_int16_t check;
    u_int32_t saddr;
    u_int32_t daddr;
    /*The options start here. */
  };

As you can see, apart from DSCP and ECN, everything has a 1 to 1 correspondence.
This is as simple as it can get.


The biggest problem you can encounter when manually forging a protocol is endianness. This becomes a problem when fields are less than 1 byte.
An example of this is in the above struct:

#if __BYTE_ORDER == __LITTLE_ENDIAN
    unsigned int ihl:4;
    unsigned int version:4;
#elif __BYTE_ORDER == __BIG_ENDIAN
    unsigned int version:4;
    unsigned int ihl:4;
#else
# error "Please fix <bits/endian.h>"

I won’t be covering the endianness in big detail, just make sure that you reverse the order of such things, luckily the protocols that are in the folder /usr/include/netinet and /usr/include/net are ready to be used. The protocols that are covered in these folders: ARP, IP, TCP, ICMP, Ethernet, UDP and some others.


Looking at the struct from earlier and the imaginary header we made earlier, let’s look at how we would use this to create a packet in C:

#include <arpa/inet.h> /* Standard for socketprogramming */
#include <netinet/ip.h> /* For the ip header */
#include <sys/types.h> /* Some macros */
#include <sys/socket.h> /* Also standard for socketprogramming */
#include <stdint.h> /* for the u_intN (N = 8, 16 or 32) types */ 

struct header {
  u_int32 src_ip;
  u_int32 dest_ip;
  u_int16 src_port;
  u_int16 dest_port;
};

struct packet {
  struct iphdr ip;
  struct header hdr;
  char data[512];
};

int main()
{
 ...
}

As you can see we created another struct, packet.
Now let’s look at the individual fields in the ip header.

  • Version
    This is the IP version, it’s either 4 or 6 (IPv4, IPv6)
  • IHL (Internet Header Length)
    This is the length of the IP header in Dwords (Dword = 32 bit). Normally this is 5 (= 160 bit).
  • ToS (Type of Service)
    This is the type of service, normally we keep these 0.
  • Total Length
    This is the length of the IP header + length of (Imaginary/udp/tcp header) + length of data, all in bytes.
  • ID (Identification)
    This is an unique id for every packet sent. It allows packets to be fragmented, all fragments have the same ID, but a different Fragment Offset
  • Fragment Offset
    This field is used to tell the position of the fragment in the original packet. This is 16 bit, or 2 bytes.
    In hexcode it would be like 0xHHHH where H would be a number. The mask for the fragmenting bits is 0x1fff.
    There are 3 bits reserverd for the FLAGS, look at the following:
#define IP_RF 0x8000                    /* reserved fragment flag */
#define IP_DF 0x4000                    /* dont fragment flag */
#define IP_MF 0x2000                    /* more fragments flag */

These flags can be OR’ed to fill the Fragment Offset field. As you might notice. 0x1fff | 0x8000 | 0x4000 | 0x2000 equals 0xffff That means you can use any value between 0x000 and 0x1fff for the fragment offset. If you’d try a value like 0x2fff for the fragment offset, that would actually be setting the more fragments flag, because: 0x2fff & 0x1fff equals 0x0fff, leaving the 0x2000 for the flags. Feel free to ask any questions about this if this was a bit hard to follow, just think of it like subnetting, there you also use a mask

  • TTL (Time to live)
    This is the amount of hops a packet can make before it get’s the destroyed, each node it passes is considered a hop. With every hop it makes the TTL get’s decremented (substracted by 1). This is to prevent packets from endlessly hopping the net.
  • Protocol
    This is the protocol that is being used, a list of protocols can be found in /usr/include/netinet/in.h:
    cat /usr/include/netinet/in.h | less I’ll just give you some useful ones here: IPPROTO_UDP and IPPROTO_TCP. These are macro’s so the corresponding values would also be legit.
  • Header Checksum
    Normally we would calculate the checksum in the header above the IP header (UDP/TCP), I’ll tell you how to calculate a checksum when we cover UDP.
  • Source Address
    This is the source IP adress (It can be anything you like ;))
  • Destination Address
    This is the destination IP adress.

Alright, now that we covered these terms, let’s look at how we go about filling these in in a C program.

#include <arpa/inet.h> /* Standard for socketprogramming */
#include <netinet/ip.h> /* For the ip header */
#include <sys/types.h> /* Some macros */
#include <sys/socket.h> /* Also standard for socketprogramming */
#include <stdint.h> /* for the u_intN (N = 8, 16 or 32) types */ 

struct header {
  u_int32 src_ip;
  u_int32 dest_ip;
  u_int16 src_port;
  u_int16 dest_port;
};

struct packet {
  struct iphdr ip;
  struct header hdr;
  char data[512];
};

#define SRCADDR "192.168.1.5"
#define DESTADDR "192.168.1.1"

int main()
{
  struct packet pk;

  pk.ip.ihl = 5;
  pk.ip.version = 4;
  pk.ip.tos = 0;
  pk.ip.id = 300; /* 300 has no meaning, 300 can now be used to identify this packet (Useful for fragmenting). */
  pk.ip.frag_off =  0x000; /* Set offset and flags to 0, this packet is not fragmented */ 
  pk.ip.ttl = 64; /* This is default value for TTL, can be more, but 64 is pretty failsafe. */
  pk.ip.protocol = IPPROTO_UDP; /* This will be a UDP packet.*/
  pk.ip.check = 0; /* Checksum will be calculated in UDP */
  pk.ip.saddr = inet_addr(SRCADDR); /* Use the command `man inet_addr` in the terminal to see what this does */
  pk.ip.daddr = inet_addr(DESTADDR);
}

As you can see we used the struct packet to declare a new struct in the main function.
It has 3 member: struct iphdr, struct header and char msg[512]
We can call assign these by using pk.ip.<field> as you see in the program, there really is no magic involved here.

UDP Header

Alright so that was the IP header, now let’s look at the UDP header. The most difficult part of forging a UDP header is calculating the checksum, but more on that later.

First let’s look at a schema for the UDP header:

As you can see this one is fairly simple. At least that is what it looks like.
The Source Port and Destination part are selfexplaining, the UDP length is in bytes and also includes the size of the data (char msg[512]). The checksum however requires it’s own tutorial, I will postpone this to the next tutorial, for now we can set it to 0.

Now let’s look at the struct in /usr/include/netinet/udp.h doing:
cat /usr/include/netinet/udp.h | less

struct udphdr
{
  __extension__ union
  {
    struct
    {
      u_int16_t uh_sport;               /* source port */
      u_int16_t uh_dport;               /* destination port */
      u_int16_t uh_ulen;                /* udp length */
      u_int16_t uh_sum;         /* udp checksum */
    };
    struct
    {
      u_int16_t source;
      u_int16_t dest;
      u_int16_t len;
      u_int16_t check;
    };
  };
};

Now don’t look at the whole union stuff and the first 4 things, just look at source, dest, len, check.
As you can see once again there is a 1 to 1 correspondence with the schema above.
Now let’s replace our imaginary header struct header with struct udphdr.
Here is what it would look like, it seems that without the checksum validation, it doesn’t get send, or I m:

#include <arpa/inet.h> /* Standard for socketprogramming */
#include <netinet/ip.h> /* For the ip header */
#include <netinet/udp.h>
#include <sys/types.h> /* Some macros */
#include <sys/socket.h> /* Also standard for socketprogramming */
#include <string.h>

struct packet {
    struct iphdr ip;
    struct udphdr udp;
    char data[512];
};

#define SRCADDR "192.168.1.10"
#define DESTADDR "192.168.1.1"

#define SRCPORT 1337
#define DESTPORT 80

int main()
{
      struct packet pk;
    memset(&pk,0,sizeof(pk));

    strcat(pk.data, "Hello, World");

    /* Filling in IP Header */
    pk.ip.ihl = 5;
    pk.ip.version = 4;
    pk.ip.tos = 0;
    pk.ip.tot_len = htons(sizeof(struct packet));
    pk.ip.id = htons(300);
    pk.ip.frag_off =  0x000;
    pk.ip.ttl = 64;
    pk.ip.protocol = IPPROTO_UDP; 
    pk.ip.check = 0;
    pk.ip.saddr = inet_addr(SRCADDR);
    pk.ip.daddr = inet_addr(DESTADDR);

    /* Filling in UDP Header */
    pk.udp.source = htons(SRCPORT);
    pk.udp.dest = htons(DESTPORT);
    pk.udp.len = htons(sizeof(pk)-sizeof(pk.ip));
    pk.udp.check = 0;

    /* Doing the socket stuff */
    int sock; 
    sock = socket(AF_INET,SOCK_RAW,IPPROTO_RAW);

    struct sockaddr_in daddr;

    daddr.sin_family = AF_INET;
    daddr.sin_addr.s_addr = inet_addr(DESTADDR);
    daddr.sin_port = htons(DESTPORT);
    memset(&daddr.sin_zero, 0, sizeof(daddr.sin_zero));

    int i;
    while(1) {
        sendto( sock, (char *) &pk, sizeof(pk), 0, (struct sockaddr *) &daddr, (socklen_t) sizeof(daddr));
    }
}

Alright it seems this program does not yet work, I think it’s because of the lacking checksum.
I will update this post when I found the fix for this. :smirk:

If anything was unclear feel free to post a comment!!

unh0lys0da


(Command-Line Ninja) #2

Such a good post. If you weren’t a mod you’d be looking at 1337 of the W33K for sure!


#3

This tutorial… it looks so well written
-wipes tears of joy-


#5

This topic was automatically closed after 30 days. New replies are no longer allowed.