Обход фильтрации HTTPS на основе SNI с очередью netfilter - PullRequest
0 голосов
/ 12 декабря 2018

Facebook и YouTube заблокированы в моей стране.Но, если я соединяюсь с openssl в Facebook, это работает без проблем.

openssl s_client -connect facebook.com:443

Если добавить -servername params в openssl, это не работает!

openssl s_client -connect facebook.com:443 -servername facebook.com

Я хочуудалить расширение SNI из каждого запроса Client Hello.Для этого я написал следующий код:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <linux/tcp.h>
//#include <netinet/in.h>
#include <linux/types.h>
#include <linux/netfilter.h>    /* for NF_ACCEPT */
#include <libnetfilter_queue/libnetfilter_queue.h>
#include <getopt.h>
#include <fcntl.h>
#include <pwd.h>

uint8_t read_uint8(unsigned char **data) {
    uint8_t value = (*data)[0];
    *data += 1;
    return value;
}

uint16_t read_uint16(unsigned char **data) {
    uint16_t value = ((*data)[0] << 8) | (*data)[1];
    *data += 2;
    return value;
}

bool proto_check_ipv4(const unsigned char *data, int len)
{
    return len >= 20 && (data[0] & 0xF0) == 0x40 && len >= ((data[0] & 0x0F) << 2);
}

void proto_skip_ipv4(unsigned char **data, int *len)
{
    int l;

    l = (**data & 0x0F) << 2;
    *data += l;
    *len -= l;
}

bool proto_check_tcp(const unsigned char *data, int len)
{
    return len >= 20 && len >= ((data[12] & 0xF0) >> 2);
}

void proto_skip_tcp(unsigned char **data,int *len)
{
    int l;

    l = ((*data)[12] & 0xF0) >> 2;
    *data += l;
    *len -= l;
}

bool proto_check_tls_handshake(const unsigned char *data, int len)
{
    return len >= 5 && data[0] == 0x16 && len >= (((data[3] << 8) | data[4]) + 5);
}

void proto_skip_tls_handshake(unsigned char **data, int *len)
{
    *len = (((*data)[3] << 8) | (*data)[4]);
    *data += 5; //handshake header length
}

bool proto_check_tls_client_hello(const unsigned char *data, int len)
{
    return len >= 4 && data[0] == 0x01 && len >= ((data[1] << 16) || (data[2] << 8) || data[3]);
}

void proto_skip_tls_client_hello(unsigned char **data, int *len) {
    *len = (((*data)[1] << 16) | ((*data)[2] << 8) | (*data)[3]);
    *data += 4; //client hello header
}

unsigned short ip_checksum(unsigned short* ip, int len){
    long sum = 0;  /* assume 32 bit long, 16 bit short */

    while(len > 1){
        sum += *ip;
        ip++;

        if(sum & 0x80000000)   /* if high order bit set, fold */
            sum = (sum & 0xFFFF) + (sum >> 16);

        len -= 2;
    }

    if(len)       /* take care of left over byte */
        sum += (unsigned short) *(unsigned char *)ip;

    while(sum>>16)
        sum = (sum & 0xFFFF) + (sum >> 16);

    return ~sum;
}

uint16_t tcp_checksum(const void *buff, int len, in_addr_t src_addr, in_addr_t dest_addr)
{
    const uint16_t *buf = (const uint16_t *)buff;
    uint16_t *ip_src=(uint16_t *)&src_addr, *ip_dst=(uint16_t *)&dest_addr;
    uint32_t sum;
    int length=len;

    // Calculate the sum
    sum = 0;
    while (len > 1)
    {
        sum += *buf++;
        if (sum & 0x80000000)
            sum = (sum & 0xFFFF) + (sum >> 16);
        len -= 2;
    }
    if ( len & 1 )
    {
        // Add the padding if the packet lenght is odd
        uint16_t v=0;
        *(uint8_t *)&v = *((uint8_t *)buf);
        sum += v;
    }

    // Add the pseudo-header
    sum += *(ip_src++);
    sum += *ip_src;
    sum += *(ip_dst++);
    sum += *ip_dst;
    sum += htons(IPPROTO_TCP);
    sum += htons(length);

    // Add the carries
    while (sum >> 16)
        sum = (sum & 0xFFFF) + (sum >> 16);

    // Return the one's complement of sum
    return (uint16_t)(~sum);
}

void tcp_fix_checksum(struct tcphdr *tcp, int len, in_addr_t src_addr, in_addr_t dest_addr)
{
    tcp->check = 0;
    tcp->check = tcp_checksum(tcp,len,src_addr,dest_addr);
}

bool processPacketData(unsigned char *data, int len)
{
    struct iphdr *iphdr = NULL;
    struct tcphdr *tcphdr = NULL;
    uint8_t proto;
    int len_tcp;
    unsigned char *phost;

    if (proto_check_ipv4(data,len))
    {
        iphdr = (struct iphdr *) data;
        proto = iphdr->protocol;
        proto_skip_ipv4(&data, &len);
    }

    if (proto !=IPPROTO_TCP || !proto_check_tcp(data,len))
        return false;

    unsigned char* ip_length_start = (unsigned char *)&iphdr->tot_len;
    int ip_total_len = read_uint16(&ip_length_start);
    ip_length_start -= 2;

    tcphdr = (struct tcphdr *) data;
    len_tcp = len;
    proto_skip_tcp(&data, &len);

    if (!proto_check_tls_handshake(data, len))
        return false;

    unsigned char* tls_length_start = data + 3;

    proto_skip_tls_handshake(&data, &len);

    int tls_len = len;

    printf("TLS len: %d\n", tls_len);
    if (!proto_check_tls_client_hello(data, len))
        return false;

    unsigned char* client_hello_length_start = data + 1;

    proto_skip_tls_client_hello(&data, &len);

    int client_hello_len = len;

    printf("Client hello length: %d\n", client_hello_len);

    data += 2; //version
    data += 32; //random

    int session_id_len = read_uint8(&data); //session id length
    data += session_id_len; //skip session id

    printf("Session length: %d\n", session_id_len);

    uint16_t cipher_suites_len = read_uint16(&data);
    data += cipher_suites_len; //skip cipher

    printf("Cipher length: %d\n", cipher_suites_len);

    uint8_t compression_methods_len = read_uint8(&data);
    data += compression_methods_len; //ckip compression_methods

    printf("Compression length: %d\n", compression_methods_len);

    unsigned char* extensions_length_start = data;

    uint16_t extensions_len = read_uint16(&data);
    uint16_t extensions_len_ = extensions_len;

    printf("EXT: %d\n", extensions_len);

    uint32_t extl = 0;
    uint8_t ext[2048] = {0};

    while(extensions_len > 0) {
        uint16_t ext_type = read_uint16(&data);
        uint16_t ext_len = read_uint16(&data);

        printf("%d %d %d\n", ext_type, ext_len, extensions_len);

        extensions_len -= 2; //ext type
        extensions_len -= 2; //ext len
        extensions_len -= ext_len;

        if (ext_type != 0) //server_name bo`lmasa
        {
            memcpy(ext + extl, data - 4, ext_len + 4);
            extl += 4 + ext_len;
        }

        data += ext_len;
    }

    extensions_length_start[0] = (uint8_t )(extl >> 8);
    extensions_length_start[1] = (uint8_t )(extl & 0xFF);

    int diff = extensions_len_ - extl;
    printf("Diff: %d\n", diff);

    tls_len -= diff;
    client_hello_len -= diff;

    printf("New TLS len: %d\n", tls_len);
    printf("New Client Hello len: %d\n", client_hello_len);

    tls_length_start[0] = (uint8_t )(tls_len >> 8);
    tls_length_start[1] = (uint8_t )(tls_len & 0xFF);

    client_hello_length_start[0] = (uint8_t )(client_hello_len >> 16);
    client_hello_length_start[1] = (uint8_t )(client_hello_len >> 8);
    client_hello_length_start[2] = (uint8_t )(client_hello_len & 0xFF);

    memcpy(extensions_length_start + 2, ext, extl);

    printf("IPHDR: %d %d\n", ip_total_len, ip_total_len-diff);
    ip_total_len -= diff;

    ip_length_start[0] = (uint8_t )(ip_total_len >> 8);
    ip_length_start[1] = (uint8_t )(ip_total_len & 0xFF);

    tcp_fix_checksum(tcphdr,len_tcp,iphdr->saddr,iphdr->daddr);

    iphdr->check = 0;
    iphdr->check = ip_checksum((unsigned short*)iphdr, 20);

    printf("Checksum: %x %x\n", tcphdr->check, iphdr->check);

    return true;
}

static int cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *cookie)
{
    int id,len;
    struct nfqnl_msg_packet_hdr *ph;
    unsigned char *data;

    ph = nfq_get_msg_packet_hdr(nfa);
    id = ph ? ntohl(ph->packet_id) : 0;

    len = nfq_get_payload(nfa, &data);

    if (len >= 0)
    {
        if (processPacketData(data, len)) {
            //            for(int i = 0; i < len; i++) {
            //                printf("%c ", data[i]);
            //            }
            //            printf("\n");
            return nfq_set_verdict(qh, id, NF_ACCEPT, len, data);
        }
    }

    return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL);
}

bool droproot(uid_t uid, gid_t gid)
{
    if (uid)
    {
        if (setgid(gid))
        {
            perror("setgid: ");
            return false;
        }
        if (setuid(uid))
        {
            perror("setuid: ");
            return false;
        }
    }
    return true;
}

int main(int argc, char **argv)
{
    struct nfq_handle *h;
    struct nfq_q_handle *qh;

    int fd;
    int rv;
    int qnum = 1;

    char buf[4096] __attribute__ ((aligned));

    uid_t uid=0;
    gid_t gid;

    printf("opening library handle\n");
    h = nfq_open();
    if (!h) {
        fprintf(stderr, "error during nfq_open()\n");
        exit(1);
    }

    printf("unbinding existing nf_queue handler for AF_INET (if any)\n");
    if (nfq_unbind_pf(h, AF_INET) < 0) {
        fprintf(stderr, "error during nfq_unbind_pf()\n");
        exit(1);
    }

    printf("binding nfnetlink_queue as nf_queue handler for AF_INET\n");
    if (nfq_bind_pf(h, AF_INET) < 0) {
        fprintf(stderr, "error during nfq_bind_pf()\n");
        exit(1);
    }

    printf("binding this socket to queue '%u'\n", qnum);
    qh = nfq_create_queue(h, qnum, &cb, NULL);
    if (!qh) {
        fprintf(stderr, "error during nfq_create_queue()\n");
        exit(1);
    }

    printf("setting copy_packet mode\n");
    if (nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff) < 0) {
        fprintf(stderr, "can't set packet_copy mode\n");
        exit(1);
    }

    fd = nfq_fd(h);

    if (droproot(uid,gid))
    {
        printf("Success\n");
        while ((rv = recv(fd, buf, sizeof(buf), 0)) && rv >= 0)
        {
            for(int i = 0; i < 12; i++) {
                printf("%x-", buf[i]);
                if (i == 6) printf("\n");
            }

            printf("\n");

            int r=nfq_handle_packet(h, buf, rv);
            if (r) fprintf(stderr,"nfq_handle_packet error %d\n",r);
        }
    }

    printf("unbinding from queue 0\n");
    nfq_destroy_queue(qh);

#ifdef INSANE
    /* normally, applications SHOULD NOT issue this command, since
    * it detaches other programs/sockets from AF_INET, too ! */
    printf("unbinding from AF_INET\n");
    nfq_unbind_pf(h, AF_INET);
#endif

    printf("closing library handle\n");
    nfq_close(h);
}

Правило iptables:

iptables -t mangle -I POSTROUTING -p tcp --dport 443 -m set --match-set zapret dst -j NFQUEUE --queue-num 1 --queue-bypass

ipset:

ipset -N zapret
ipset -A zapret usa.gov

Я использую tshark для анализа:

tshark -i eth1 -f "dst 54.85.132.205"

Все отлично работает, кроме одного:

Capturing on 'eth1'
1 0.000000000 * → 54.85.132.205 TCP 74 48852 → 443 [SYN] Seq=0 Win=29200 Len=0 MSS=1460 SACK_PERM=1 TSval=44927313 TSecr=0 WS=128
2 0.188082035 * → 54.85.132.205 TCP 66 48852 → 443 [ACK] Seq=1 Ack=1 Win=29312 Len=0 TSval=44927360 TSecr=152450118
3 0.188441788 * → 54.85.132.205 TLSv1 258 Client Hello [ETHERNET FRAME CHECK SEQUENCE INCORRECT]

Почему tshark выдает ошибку [ETHERNET FRAME CHECK SEQUENCE INCORRECT]!

Я хотел изменить FCS Ethernet, но не смог.

Как настроить FCS Ethernet?

Или, где я могу найти инструмент для удаления SNI из запроса?

Обновление:

Я изменяю значение server_name, но оно не работает.

...
proto_skip_tls_client_hello(&data, &len);

if ((phost = find_bin(data, len, "usa.gov", 7))) 
{
    phost[0] = 'b';
    printf("Found: %.7s\n", phost);
}
...

Это работает:

openssl s_client -connect youtube.com:443 -servername buotube.com
...