7

I am experimenting with raw sockets and I have just written a small program that sends TCP packets with the syn flag set. I can see the packets coming with Wireshark on the server side and they look good, but the server never responds with any syn-ack packets.

I have compared the syn packets that my program constructs (see code below) with the ones that hping3 sends (because the packets of hping3 always get a syn-ack). The only that differs between my syn packets and hping3's syn packets are the ip identification number, tcp source port (which is randomized in hping3), tcp sequence number (which is also randomized in hping3) and the ip checksum field. All these four fields are based on some random numbers, that is why they differ. All other fields are equal! But my program does not get any syn-acks but hping3 does!

I am using Kali Linux for sending the packets (of course as root) and CentOS for the server.

Have I missed something essential or just missunderstood anything?

Removed code

EDIT
Here is the entire packet captured by Wireshark on the client side (divided into 4 images below). Note that the packets sent by hping3 are totally equal except the values for ip identification, source port, sequence number and checksum:

Images removed


Here is the hex dump of the packet.

Hexdump removed

EDIT 2

Ok, now I have created the pseudo header according to RFC793. The pseudo header is just used for the tcp checksum calculation. Now the IP header seems to be correct, but Wireshark complains about that the packet does not contain a full TCP header and it really seems corrupted because some of the fields contains strange values that I have not set.

First I allocate a buffer (called tcp_header) with space for the tcp header and the pseudo header. Second, I create a buffer for the ip header containing space for ip, tcp and pseudo headers.
First I fill the tcp_header with its data and then I copy it to the ip_header before sending it with the sendto function.

Does something go wrong when I copy the contents of tcp_packet to ip_packet or am I doing something else wrong?

#include <cstdlib>
#include <stdio.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#define __FAVOR_BSD 1
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>
#include <sys/ioctl.h>
#include <string.h>
#include <iostream>
#include <net/ethernet.h>
#include <time.h>

#define PCKT_LEN 1024

struct pseudohdr
{
    __u32 saddr;
    __u32 daddr;
    __u8  zero;
    __u8  protocol;
    __u16 lenght;
};

#define PSEUDOHDR_SIZE sizeof(struct pseudohdr)

unsigned short csum(unsigned short *buf, int len) {
    unsigned long sum;
    for(sum=0; len>0; len-=2)
        sum += *buf++;
    sum = (sum >> 16) + (sum &0xffff);
    sum += (sum >> 16);
    return (unsigned short)(~sum);
}


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

    srand(time(NULL));

    char *ip_packet = new char[sizeof(struct iphdr) + 
                               sizeof(struct tcphdr)]();

    char *tcp_packet = new char[sizeof(struct pseudohdr) + 
                                sizeof(struct tcphdr)]();

    struct pseudohdr *pseudoheader = (struct pseudohdr*) tcp_packet;
    class tcphdr *tcp = (struct tcphdr *) (tcp_packet + sizeof(struct pseudohdr));
    class iphdr *ip = (struct iphdr *) ip_packet;


    class sockaddr_in sin, din;

    int sd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
    if(sd < 0) {
       perror("socket() error");
       exit(-1);
    } else {
        printf("socket()-SOCK_RAW and tcp protocol is OK.\n");
    }

    // Randomize src port
    int srcport = rand()%100+25000;

    sin.sin_family = AF_INET;           // Address family
    sin.sin_addr.s_addr = inet_addr("192.168.2.80");
    sin.sin_port = htons(srcport); // Source port

    din.sin_family = AF_INET;
    din.sin_addr.s_addr = inet_addr("192.168.2.6");
    din.sin_port = htons(80); // Destination port

    /* tcp pseudo header */
    memcpy(&pseudoheader->saddr, &sin.sin_addr.s_addr, 4);
    memcpy(&pseudoheader->daddr, &din.sin_addr.s_addr, 4);
    pseudoheader->protocol      = 6; /* tcp */
    pseudoheader->lenght        = htons(sizeof(struct pseudohdr) + sizeof(struct tcphdr));


    ip->ihl = 5;
    ip->version = 4;
    ip->tos = 0;
    ip->tot_len = sizeof(class iphdr) + sizeof(class tcphdr);
    ip->id = htons((getpid() & 255) + rand()%100+30000);
    ip->frag_off = 0;
    ip->ttl = 32;
    ip->protocol = 6; // TCP
    ip->check = 0; // Done by kernel
    memcpy(&ip->saddr, (char*)&sin.sin_addr, sizeof(ip->saddr));
    memcpy(&ip->daddr, (char*)&din.sin_addr, sizeof(ip->daddr));


    // The TCP structure
    tcp->th_sport = htons(srcport);
    tcp->th_dport = htons(80);      // Destination port
    tcp->th_seq = htonl(rand()%100+1000);
    tcp->th_ack = htonl(rand()%30);
    tcp->th_off = 5;
    tcp->th_flags = TH_SYN;
    tcp->th_win = htons(1024);
    tcp->th_urp = 0;

    // Now calculate tcp checksum
    tcp->th_sum = csum((unsigned short *) tcp_packet, sizeof(struct pseudohdr) + sizeof(struct tcphdr));

    // Copy tcp_packet to ip_packet
    memcpy(ip_packet + sizeof(struct iphdr), tcp_packet+sizeof(struct pseudohdr), sizeof(struct tcphdr));

    // Bind socket to interface
    int one = 1;
    const int *val = &one;
    const char opt[] = "eth0";

    if(setsockopt(sd, IPPROTO_IP, IP_HDRINCL, (char *)&one, sizeof(one)) < 0) {
        perror("setsockopt() error");
        exit(-1);
    }
    else
        printf("setsockopt() is OK\n");

    if(sendto(sd, ip_packet, ip->tot_len, 0, (sockaddr*)&din, sizeof(din)) < 0) {
       perror("sendto() error");
       exit(-1);
    }
    else
        printf("Send OK!");

    close(sd);
    return 0;
}

The tcp contents of the packet:

Images removed

Edit 3

Now I have found something interesting. Study the cheksums on this picture: enter image description here

The checksum is in network order and shall thus be read in reversed order, as 0x06c0 (and not is as it is stated above as 0xc006). That is equal to the decimal value of 1728. Wireshark says the correct cheksum should be 0x12c0 which gives a decimal value of 4800.
4800-1728=3072. That is the difference between the actual checksum and the correct checksum calculated by Wireshark in all packets that is sent by my program.

So, if I simply add that value to the cheksum result:

tcp->th_sum = csum((unsigned short *) tcp_packet, sizeof(struct pseudohdr) + sizeof(struct tcphdr)) + 3072;

...then all packets get the correct checksum and receives a corresponding SYN-ACK.

Why the magic number 3072???

Rox
  • 2,647
  • 15
  • 50
  • 85
  • 1
    Perhaps the checksum is wrong? (I'm not saying it *is*, but that would be the first thing I'd check if this were my debugging issue...) – Oliver Charlesworth Jan 03 '14 at 01:15
  • Well, I have implemented the checksum calculation in the same way as all tutorials on raw sockets I have found on the Internet, that is: `csum((unsigned short *) buffer, (sizeof(class iphdr) + sizeof(class tcphdr)))`. The `csum` function is at the top in the code above. – Rox Jan 03 '14 at 01:24
  • If Wireshark says it's good, that's a good sign ;) – Oliver Charlesworth Jan 03 '14 at 01:26
  • Just to make sure, you are looking at Wireshark on the Server side, right? Sometimes ethernet cards monkey with packets on the way out. They can modify things like checksums, which can be very frustrating if you don't know about it. – kmort Jan 03 '14 at 03:51
  • @kmort: I have now checked and compared every single field (including checksum) in the packets from the sending side with the ones on the server side with Wireshark. Everything is totally equal. I have also compared hping3´s packets on both the client and server side, the fields are also equal on both sides. The only difference is that the hping3´s packets get a `SYN-ACK` response but mine don´t. Strange, huh! :-) – Rox Jan 03 '14 at 10:41
  • Try randomizing yor sequence number. – n. m. could be an AI Jan 03 '14 at 11:44
  • @n.m.: Tried it but without success. :-( See my edit in the code above where I set the sequence number. – Rox Jan 03 '14 at 15:04
  • Post the entire wireshark decode including the hex dump of the packet – Brian Walker Jan 03 '14 at 15:11
  • @BrianWalker: Here you go (look at my edit above). – Rox Jan 03 '14 at 15:59
  • 1
    It says it, right there. It's even highlighted in yellow. You're setting a nonzero acknowledged sequence number when you're not ACKing anything. The server's dropping your invalid packet. – jthill Jan 03 '14 at 16:16
  • The TCP checksum is even 0, quite implausible (but could be an artifact of capturing) – nos Jan 03 '14 at 16:25
  • @jthill: am aware of that. Unfortunately, it doesn´t help if I set it to zero. Also `hping3`s packets get this warning since the packets also contain non-zero values for the `SYN` packets. So it must be something else. – Rox Jan 03 '14 at 16:26
  • @nos: I have also tried to set the checksum for the `tcp checksum field` using the same function as for the `ip checksum field`, but it didn´t work. – Rox Jan 03 '14 at 16:27
  • @rox well, you need to generate a proper checksum. And verify that you generated it properly. The other end is 100% surely going to drop the packet if the checksum (IP or TCP) doesn't match. Remember that the TCP checksum needs to be generated on a pseudo header. Wireshark have settings so it can verify the checksum for you. – nos Jan 03 '14 at 16:28
  • @nos: Now I just discovered a checkbox in Wireshark that enables the validation of `tcp checksums` and I can now see that mine are incorrect and hping3´s are correct. So it must be my checksum calculation that fails. If someone knows how to do it correctly, please let me know. On a pseudo header? I will read about that. That is something I have missed I think! – Rox Jan 03 '14 at 16:34
  • [rfc793](http://www.ietf.org/rfc/rfc793.txt) – jthill Jan 03 '14 at 16:41
  • @nos: I have now rewritten my code so it uses a pseudo header for the cheksum calulation (tcp header + pseudo header). Now wireshark complains about an incomplete tcp header, but the ip header seems ok. Can you see anything that I have made in the wrong way? – Rox Jan 03 '14 at 18:02
  • Is the hexdump the packet from the new code or the old code? Post the wireshark output from the new code. – Brian Walker Jan 03 '14 at 18:30
  • @BrianWalker: I have now uploaded a screen dump of Wireshark covering the tcp part of the packet. I have also uploaded the hex dump. Please, look at my updated `edit 2`. – Rox Jan 03 '14 at 18:52
  • 2
    The length is correct but where the TCP should start (offset 22h) the source and destination IP addresses are repeated. The memcpy to copy the tcp packet to the ip packet would be the first place to look. – Brian Walker Jan 03 '14 at 18:53
  • @BrianWalker: I just succeeded calculating the correct checksum, but I still have a question about the calculation. Pleade look at my edit 3. – Rox Jan 03 '14 at 23:21
  • 2
    3072 = 2048 + 1024. Does that suggest anything to you? – user207421 Jan 03 '14 at 23:24
  • @EJP: Uhm... I don´t know if it the time (12.35 AM) that stops my brain from thinking. :-) 3072/8=384 bytes of something? The whole ethernet frame is just 54 bytes and the tcp header and ip header is 20 bytes each. I really can´t figure it out right now. :-) – Rox Jan 03 '14 at 23:35

1 Answers1

1

I am not content with the check sum algorithm you are using. The one suggested by Stevens:

uint16_t
in_cksum(uint16_t *addr, int len)
{
        int                     nleft = len;
        uint32_t                sum = 0;
        uint16_t                *w = addr;
        uint16_t                answer = 0;

        /*
         * Our algorithm is simple, using a 32 bit accumulator (sum), we add
         * sequential 16 bit words to it, and at the end, fold back all the
         * carry bits from the top 16 bits into the lower 16 bits.
         */
        while (nleft > 1)  {
                sum += *w++;
                nleft -= 2;
        }

                /* 4mop up an odd byte, if necessary */
        if (nleft == 1) {
                *(unsigned char *)(&answer) = *(unsigned char *)w ;
                sum += answer;
        }

                /* 4add back carry outs from top 16 bits to low 16 bits */
        sum = (sum >> 16) + (sum & 0xffff);     /* add hi 16 to low 16 */
        sum += (sum >> 16);                     /* add carry */
        answer = ~sum;                          /* truncate to 16 bits */
        return(answer);
}
wick
  • 1,995
  • 2
  • 20
  • 31