Подделка SYN-ACK ответа и вычисление контрольной суммы TCP-пакета

Для имитации установления TCP-соединения (3-х этапного рукопожатия) можно воспользоваться простым алгоритмом.

На пакет от клиента с выставленным SYN-битом с сервера отправляется тот же пакет с выставленными SYN и ACK-битами. Ждем от клиента подтверждения с битом ACK и все, можно считать что с точки зрения клиента соединение установлено. Такой трюк можно использовать при zero-copy обработке сетевого трафика. Псевдокодом это может быть описано так:


/* Пакет с ethernet, IP и TCP заголовками */
struct tcp_hdr
{
        struct ether_header ether;
        struct iphdr ip;
        struct tcphdr tcp;
        uint8_t payload[1];
} __attribute__ ((packed));

tcp_reply(struct tcp_hdr *tcp, ...)
{
        /* ... */
        if (tcp->tcp.syn && !tcp->tcp.ack) {
                /* в пакете есть флаг SYN и нет ACK */
                uint8_t hwbuf[ETH_ALEN]; /* буфер для обмена ethernet-адресов */
                uint32_t addrbuf; /* IPv4-адресов */
                uint16_t portbuf; /* и портов */

                /* меняем местами IP адреса */
                addrbuf = tcp->ip.saddr;
                tcp->ip.saddr = tcp->ip.daddr;
                tcp->ip.daddr = addrbuf;

                /* TCP порты */
                portbuf = tcp->tcp.source;
                tcp->tcp.source = tcp->tcp.dest;
                tcp->tcp.dest = portbuf;

                /* выставляем ACK-бит */
                tcp->tcp.ack = 1;
                /* и номера последовательностей */
                tcp->tcp.ack_seq = htonl(ntohl(tcp->tcp.seq) + 1);
                tcp->tcp.seq = my_seq_gen();

                tcp_recalc_cksum(tcp);

                /* меняем местами ethernet-адреса */
                memcpy(hwbuf, tcp->ether.ether_shost, ETH_ALEN);
                memcpy(tcp->ether.ether_shost, tcp->ether.ether_dhost, ETH_ALEN);
                memcpy(tcp->ether.ether_dhost, hwbuf, ETH_ALEN);

                /* иньектируем модифицированный пакет обратно в сеть */
                my_inject(interface, tcp, len);
        }
        /* ... */
}

Функция my_seq_gen() должна генерировать начальную TCP-последовательность. Это может быть произвольно выбранное 32-битное число (в этом случае нужно связать его с TCP-сессией и хранить для дальнейшего использования) или SYN-cookie, вычисляемый из параметров сессии (см. https://en.wikipedia.org/wiki/SYN_cookies )

Код для вычисления контрольной суммы TCP-пакета(проверки для простоты опущены). Вычислять будем без генерации псевдо-заголовка и без копирования пакета. Результат пишем в прямо TCP-заголовок пакета


void tcp_recalc_cksum(struct tcp_hdr *tcp)
{
        int i;
        uint16_t *iter;
        uint32_t sum = 0;
        uint16_t pslen;

        pslen = ntohs(tcp->ip.tot_len) - tcp->ip.ihl * 4;
        /* считаем контрольную сумму IP адресов, они находятся в заголовке IP-уровня пакета */
        iter = (uint16_t *)(&tcp->ip.saddr);
        for (i=0; itcp.check = 0;
        /* проходим по пакету начиная с TCP-заголовка */
        iter = (uint16_t *)(&tcp->tcp);
        while (pslen > 1) {
                sum += *iter;
                iter++;
                pslen -= sizeof(uint16_t);
        }
        /* добавляем последний байт, если размер пакета нечетный */
        if (pslen) {
                sum += *((uint8_t *)iter);
        }

        sum = (sum >> 16) + (sum & 0xffff);
        sum += (sum >> 16);
        /* записываем новую контрольную сумму */
        tcp->tcp.check = (uint16_t)(~sum);
}

Blog: