commit 02896c5008d24262e4b798e7fbe015d1af7cddda Author: bol-van Date: Fri Apr 4 09:44:20 2025 +0300 clear diff --git a/ipobfs/Makefile b/ipobfs/Makefile new file mode 100644 index 0000000..ba2c9a2 --- /dev/null +++ b/ipobfs/Makefile @@ -0,0 +1,12 @@ +CC ?= gcc +CFLAGS ?= -s -O3 +LIBS = -lnetfilter_queue -lnfnetlink -lcap +SRC_FILES = *.c + +all: ipobfs + +ipobfs: $(SRC_FILES) + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(LIBS) + +clean: + rm -f ipobfs *.o diff --git a/ipobfs/checksum.c b/ipobfs/checksum.c new file mode 100644 index 0000000..47d8818 --- /dev/null +++ b/ipobfs/checksum.c @@ -0,0 +1,118 @@ +#include "checksum.h" +#include + +//#define htonll(x) ((1==htonl(1)) ? (x) : ((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32)) +//#define ntohll(x) ((1==ntohl(1)) ? (x) : ((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32)) + +static uint16_t from64to16(uint64_t x) +{ + uint32_t u = (uint32_t)(uint16_t)x + (uint16_t)(x>>16) + (uint16_t)(x>>32) + (uint16_t)(x>>48); + return (uint16_t)u + (uint16_t)(u>>16); +} + +static uint16_t do_csum(const uint8_t * buff, size_t len) +{ + uint8_t odd; + size_t count; + uint64_t result,w,carry=0; + uint16_t u16; + + if (len <= 0) return 0; + odd = (uint8_t)(1 & (size_t)buff); + if (odd) + { + // any endian compatible + u16 = 0; + *((uint8_t*)&u16+1) = *buff; + result = u16; + len--; + buff++; + } + else + result = 0; + count = len >> 1; /* nr of 16-bit words.. */ + if (count) + { + if (2 & (size_t) buff) + { + result += *(uint16_t *) buff; + count--; + len -= 2; + buff += 2; + } + count >>= 1; /* nr of 32-bit words.. */ + if (count) + { + if (4 & (size_t) buff) + { + result += *(uint32_t *) buff; + count--; + len -= 4; + buff += 4; + } + count >>= 1; /* nr of 64-bit words.. */ + if (count) + { + do + { + w = *(uint64_t *) buff; + count--; + buff += 8; + result += carry; + result += w; + carry = (w > result); + } while (count); + result += carry; + result = (result & 0xffffffff) + (result >> 32); + } + if (len & 4) + { + result += *(uint32_t *) buff; + buff += 4; + } + } + if (len & 2) + { + result += *(uint16_t *) buff; + buff += 2; + } + } + if (len & 1) + { + // any endian compatible + u16 = 0; + *(uint8_t*)&u16 = *buff; + result += u16; + } + u16 = from64to16(result); + if (odd) u16 = ((u16 >> 8) & 0xff) | ((u16 & 0xff) << 8); + return u16; +} + +uint16_t csum_partial(const void *buff, size_t len) +{ + return do_csum(buff,len); +} + +uint16_t csum_tcpudp_magic(uint32_t saddr, uint32_t daddr, size_t len, uint8_t proto, uint16_t sum) +{ + return ~from64to16((uint64_t)saddr + daddr + sum + htonl(len+proto)); +} + +uint16_t ip4_compute_csum(const void *buff, size_t len) +{ + return ~from64to16(do_csum(buff,len)); +} +void ip4_fix_checksum(struct iphdr *ip) +{ + ip->check = 0; + ip->check = ip4_compute_csum(ip, ip->ihl<<2); +} + +uint16_t csum_ipv6_magic(const void *saddr, const void *daddr, size_t len, uint8_t proto, uint16_t sum) +{ + uint64_t a = (uint64_t)sum + htonl(len+proto) + + *(uint32_t*)saddr + *((uint32_t*)saddr+1) + *((uint32_t*)saddr+2) + *((uint32_t*)saddr+3) + + *(uint32_t*)daddr + *((uint32_t*)daddr+1) + *((uint32_t*)daddr+2) + *((uint32_t*)daddr+3); + return ~from64to16(a); +} diff --git a/ipobfs/checksum.h b/ipobfs/checksum.h new file mode 100644 index 0000000..1a6c9d3 --- /dev/null +++ b/ipobfs/checksum.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include +#include +#include + +uint16_t csum_partial(const void *buff, size_t len); +uint16_t csum_tcpudp_magic(uint32_t saddr, uint32_t daddr, size_t len, uint8_t proto, uint16_t sum); +uint16_t csum_ipv6_magic(const void *saddr, const void *daddr, size_t len, uint8_t proto, uint16_t sum); +uint16_t ip4_compute_csum(const void *buff, size_t len); +void ip4_fix_checksum(struct iphdr *ip); diff --git a/ipobfs/ipobfs.c b/ipobfs/ipobfs.c new file mode 100644 index 0000000..85d7d95 --- /dev/null +++ b/ipobfs/ipobfs.c @@ -0,0 +1,615 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "checksum.h" + +#define NF_DROP 0 +#define NF_ACCEPT 1 + + + +typedef enum +{ + none = 0, fix, valid +} csum_mode; + +struct params_s +{ + bool debug; + csum_mode csum; + int qnum; + uint8_t ipp_xor; + uint32_t data_xor; + size_t data_xor_offset, data_xor_len; +}; + +struct params_s params; + + +static bool proto_check_ipv4(uint8_t *data, size_t len) +{ + return len >= 20 && (data[0] & 0xF0) == 0x40 && + len >= ((data[0] & 0x0F) << 2); +} +// move to transport protocol +static void proto_skip_ipv4(uint8_t **data, size_t *len) +{ + size_t l; + + l = (**data & 0x0F) << 2; + *data += l; + *len -= l; +} + +static bool proto_check_ipv6(uint8_t *data, size_t len) +{ + return len >= 40 && (data[0] & 0xF0) == 0x60 && + (len - 40) >= htons(*(uint16_t*)(data + 4)); // payload length +} +static void proto_skip_ipv6_base_header(uint8_t **data, size_t *len) +{ + *data += 40; *len -= 40; // skip ipv6 base header +} + + +static bool ip4_fragmented(struct iphdr *ip) +{ + // fragment_offset!=0 or more fragments flag + return !!(ntohs(ip->frag_off) & 0x3FFF); +} +static uint16_t ip4_frag_offset(struct iphdr *ip) +{ + return (ntohs(ip->frag_off) & 0x1FFF)<<3; +} + + +static void fix_transport_checksum(struct iphdr *ip, struct ip6_hdr *ip6, uint8_t *tdata, size_t tlen) +{ + uint8_t proto; + uint16_t check, check_old; + + if (!!ip == !!ip6) return; // must be only one + + if (ip && ip4_fragmented(ip)) + { + if (params.debug) printf("fix_transport_checksum not fixing checksum in fragmented ip\n"); + return; // no way we can compute valid checksum for ip fragment + } + + proto = ip ? ip->protocol : ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt; + switch (proto) + { + case IPPROTO_TCP: + if (tlen < sizeof(struct tcphdr)) return; + check_old = ((struct tcphdr*)tdata)->check; + ((struct tcphdr*)tdata)->check = 0; + break; + case IPPROTO_UDP: + if (tlen < sizeof(struct udphdr)) return; + check_old = ((struct udphdr*)tdata)->check; + ((struct udphdr*)tdata)->check = 0; + break; + default: + return; + } + check = ip ? csum_tcpudp_magic(ip->saddr,ip->daddr,tlen,proto,csum_partial(tdata, tlen)) : csum_ipv6_magic(&ip6->ip6_src,&ip6->ip6_dst,tlen,proto,csum_partial(tdata, tlen)); + switch (proto) + { + case IPPROTO_TCP: + ((struct tcphdr*)tdata)->check = check; + break; + case IPPROTO_UDP: + ((struct udphdr*)tdata)->check = check; + break; + } + if (params.debug) printf("fix_transport_checksum pver=%c proto=%u %04X => %04X\n", ip ? '4' : '6', proto, check_old, check); + +} + + +static uint32_t rotl32(uint32_t value, unsigned int count) +{ + return value << count | value >> (32 - count); +} +static uint32_t rotr32 (uint32_t value, unsigned int count) +{ + return value >> count | value << (32 - count); +} +// this function can xor multi-chunked payload. data point to a chunk, len means chunk length, data_pos tells byte offset of this chunk +// on some architectures misaligned access cause exception , kernel transparently fixes it, but it costs huge slowdown - 15-20 times slower +static void _modify_packet_payload(uint8_t *data,size_t len,size_t data_pos, uint32_t data_xor, size_t data_xor_offset, size_t data_xor_len) +{ + if (!data_xor_len) data_xor_len=0xFFFF; + if (data_xor_offset<(data_pos+len) && (data_xor_offset+data_xor_len)>data_pos) + { + size_t start=data_xor_offset>data_pos ? data_xor_offset-data_pos : 0; + if (start=8 ; len-=8,data+=8) *(uint64_t*)data ^= nxor; + if (len>=4) + { + *(uint32_t*)data ^= (uint32_t)nxor; + len-=4; data+=4; + } + } + while(len--) *data++ ^= (uint8_t)(xor=rotl32(xor,8)); + } + } +} +static void modify_packet_payload(struct iphdr *ip, struct ip6_hdr *ip6, uint8_t *tdata, size_t tlen, int indev, int outdev) +{ + if (tlen > params.data_xor_offset) + { + if (params.debug) printf("modify_packet_payload data_xor %08X\n", params.data_xor); + + _modify_packet_payload(tdata,tlen, ip ? ip4_frag_offset(ip) : 0,params.data_xor,params.data_xor_offset,params.data_xor_len); + + // incoming packets : we cant disable sum check in kernel. instead we forcibly make checksum valid + // if indev==0 it means packet was locally generated. no need to fix checksum because its supposed to be valid + if ((params.csum == valid || params.csum == fix && indev)) fix_transport_checksum(ip, ip6, tdata, tlen); + } +} + + +static bool modify_ip4_packet(uint8_t *data, size_t len, int indev, int outdev) +{ + bool bRes = false; + uint8_t bOutgoing=!indev; + struct iphdr *iphdr = (struct iphdr*)data; + + // do data modification with original ip protocol. necessary for checksums + for(uint8_t b=0;b<=1;b++) + { + if (params.data_xor && b!=bOutgoing) + { + uint8_t *tdata = data; + size_t tlen = len; + proto_skip_ipv4(&tdata, &tlen); + modify_packet_payload(iphdr, NULL, tdata, tlen, indev, outdev); + bRes = true; + } + if (params.ipp_xor && b==bOutgoing) + { + uint8_t proto = iphdr->protocol; + iphdr->protocol ^= params.ipp_xor; + if (params.debug) printf("modify_ipv4_packet proto %u=>%u\n", proto, iphdr->protocol); + ip4_fix_checksum(iphdr); + bRes = true; + } + } + return bRes; +} +static bool modify_ip6_packet(uint8_t *data, size_t len, int indev, int outdev) +{ + bool bRes = false; + uint8_t bOutgoing=!indev; + struct ip6_hdr *ip6hdr = (struct ip6_hdr*)data; + + // do data modification with original ip protocol. necessary for checksums + for(uint8_t b=0;b<=1;b++) + { + if (params.data_xor && b!=bOutgoing) + { + uint8_t *tdata = data; + size_t tlen = len; + proto_skip_ipv6_base_header(&tdata, &tlen); + modify_packet_payload(NULL, ip6hdr, tdata, tlen, indev, outdev); + bRes = true; + } + if (params.ipp_xor && b==bOutgoing) + { + uint8_t proto = ip6hdr->ip6_ctlun.ip6_un1.ip6_un1_nxt; + ip6hdr->ip6_ctlun.ip6_un1.ip6_un1_nxt ^= params.ipp_xor; + if (params.debug) printf("modify_ipv6_packet proto %u=>%u\n", proto, ip6hdr->ip6_ctlun.ip6_un1.ip6_un1_nxt); + bRes = true; + } + } + return bRes; +} + + +typedef enum +{ + pass = 0, modify, drop +} packet_process_result; +static packet_process_result processPacketData(uint8_t *data_pkt, size_t len_pkt, int indev, int outdev) +{ + struct iphdr *iphdr = NULL; + struct ip6_hdr *ip6hdr = NULL; + bool bMod = false; + + if (proto_check_ipv4(data_pkt, len_pkt)) + bMod = modify_ip4_packet(data_pkt, len_pkt, indev, outdev); + else if (proto_check_ipv6(data_pkt, len_pkt)) + bMod = modify_ip6_packet(data_pkt, len_pkt, indev, outdev); + return bMod ? modify : pass; +} + + +static int cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *cookie) +{ + __be32 id; + size_t len; + struct nfqnl_msg_packet_hdr *ph; + uint8_t *data; + + ph = nfq_get_msg_packet_hdr(nfa); + id = ph ? ntohl(ph->packet_id) : 0; + + len = nfq_get_payload(nfa, &data); + if (params.debug) printf("packet: id=%d len=%zu\n", id, len); + if (len >= 0) + { + switch (processPacketData(data, len, nfq_get_indev(nfa), nfq_get_outdev(nfa))) + { + case modify: return nfq_set_verdict(qh, id, NF_ACCEPT, len, data); + case drop: return nfq_set_verdict(qh, id, NF_DROP, 0, NULL); + } + } + + return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); +} + +static bool setpcap(cap_value_t *caps, int ncaps) +{ + cap_t capabilities; + + if (!(capabilities = cap_init())) + return false; + + if (ncaps && (cap_set_flag(capabilities, CAP_PERMITTED, ncaps, caps, CAP_SET) || + cap_set_flag(capabilities, CAP_EFFECTIVE, ncaps, caps, CAP_SET))) + { + cap_free(capabilities); + return false; + } + if (cap_set_proc(capabilities)) + { + cap_free(capabilities); + return false; + } + cap_free(capabilities); + return true; +} +static int getmaxcap() +{ + int maxcap = CAP_LAST_CAP; + FILE *F = fopen("/proc/sys/kernel/cap_last_cap", "r"); + if (F) + { + int n = fscanf(F, "%d", &maxcap); + fclose(F); + } + return maxcap; + +} +static bool dropcaps() +{ + // must have CAP_SETPCAP at the end. its required to clear bounding set + cap_value_t cap_values[] = { CAP_NET_ADMIN,CAP_SETPCAP }; + int capct = sizeof(cap_values) / sizeof(*cap_values); + int maxcap = getmaxcap(); + + if (setpcap(cap_values, capct)) + { + for (int cap = 0; cap <= maxcap; cap++) + { + if (cap_drop_bound(cap)) + { + fprintf(stderr, "could not drop cap %d\n", cap); + perror("cap_drop_bound"); + } + } + } + // now without CAP_SETPCAP + if (!setpcap(cap_values, capct - 1)) + { + perror("setpcap"); + return false; + } + return true; +} +static bool droproot(uid_t uid, gid_t gid) +{ + if (uid || gid) + { + if (prctl(PR_SET_KEEPCAPS, 1L)) + { + perror("prctl(PR_SET_KEEPCAPS): "); + return false; + } + if (setgid(gid)) + { + perror("setgid: "); + return false; + } + if (setuid(uid)) + { + perror("setuid: "); + return false; + } + } + return dropcaps(); +} + +static void daemonize() +{ + int pid; + + pid = fork(); + if (pid == -1) + { + perror("fork: "); + exit(2); + } + else if (pid != 0) + exit(0); + + if (setsid() == -1) + exit(2); + if (chdir("/") == -1) + exit(2); + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + /* redirect fd's 0,1,2 to /dev/null */ + open("/dev/null", O_RDWR); + int fd; + /* stdin */ + fd = dup(0); + /* stdout */ + fd = dup(0); + /* stderror */ +} + +static bool writepid(const char *filename) +{ + FILE *F; + if (!(F = fopen(filename, "w"))) + return false; + fprintf(F, "%d", getpid()); + fclose(F); + return true; +} + + +static void exithelp() +{ + printf( + " --qnum=\n" + " --daemon\t\t\t; daemonize\n" + " --pidfile=\t\t; write pid to file\n" + " --user=\t\t; drop root privs\n" + " --debug\t\t\t; print debug info\n" + " --uid=uid[:gid]\t\t; drop root privs\n" + " --ipproto-xor=0..255|0x00-0xFF\t; xor protocol ID with given value\n" + " --data-xor=0xDEADBEAF\t\t; xor IP payload (after IP header) with 32-bit HEX value\n" + " --data-xor-offset=\t; start xoring at specified position after IP header end\n" + " --data-xor-len=\t\t; xor block max length. xor entire packet after offset if not specified\n" + " --csum=none|fix|valid\t\t; transport header checksum : none = dont touch, fix = ignore checksum on incoming packets, valid = always make checksum valid\n" + ); + exit(1); +} + +int main(int argc, char **argv) +{ + struct nfq_handle *h; + struct nfq_q_handle *qh; + int fd; + int rv; + char buf[16384] __attribute__((aligned)); + int option_index = 0; + int v; + bool daemon = false; + uid_t uid = 0; + gid_t gid = 0; + char pidfile[256]; + + srand(time(NULL)); + + memset(¶ms, 0, sizeof(params)); + params.data_xor_len = 0xFFFF; + *pidfile = 0; + + const struct option long_options[] = { + {"qnum",required_argument,0,0}, // optidx=0 + {"daemon",no_argument,0,0}, // optidx=1 + {"pidfile",required_argument,0,0}, // optidx=2 + {"user",required_argument,0,0 },// optidx=3 + {"uid",required_argument,0,0 },// optidx=4 + {"debug",no_argument,0,0 },// optidx=5 + {"ipproto-xor",required_argument,0,0}, // optidx=6 + {"data-xor",required_argument,0,0}, // optidx=7 + {"data-xor-offset",required_argument,0,0}, // optidx=8 + {"data-xor-len",required_argument,0,0}, // optidx=9 + {"csum",required_argument,0,0}, // optidx=10 + {NULL,0,NULL,0} + }; + if (argc < 2) exithelp(); + while ((v = getopt_long_only(argc, argv, "", long_options, &option_index)) != -1) + { + if (v) exithelp(); + switch (option_index) + { + case 0: /* qnum */ + params.qnum = atoi(optarg); + if (params.qnum < 0 || params.qnum>65535) + { + fprintf(stderr, "bad qnum\n"); + exit(1); + } + break; + case 1: /* daemon */ + daemon = true; + break; + case 2: /* pidfile */ + strncpy(pidfile, optarg, sizeof(pidfile)); + pidfile[sizeof(pidfile) - 1] = '\0'; + break; + case 3: /* user */ + { + struct passwd *pwd = getpwnam(optarg); + if (!pwd) + { + fprintf(stderr, "non-existent username supplied\n"); + exit(1); + } + uid = pwd->pw_uid; + gid = pwd->pw_gid; + break; + } + case 4: /* uid */ + gid = 0x7FFFFFFF; // default git. drop gid=0 + if (!sscanf(optarg, "%u:%u", &uid, &gid)) + { + fprintf(stderr, "--uid should be : uid[:gid]\n"); + exit(1); + } + break; + case 5: /* debug */ + params.debug = true; + break; + case 6: /* ipproto-xor */ + { + uint u; + if (!sscanf(optarg, "0x%X", &u) && !sscanf(optarg, "%u", &u) || u > 255) + { + fprintf(stderr, "ipp-xor should be 1-byte decimal or 0x\n"); + exit(1); + } + params.ipp_xor = (uint8_t)u; + } + break; + case 7: /* data-xor */ + if (!sscanf(optarg, "0x%X", ¶ms.data_xor)) + { + fprintf(stderr, "data-xor should be 32 bit HEX starting with 0x\n"); + exit(1); + } + break; + case 8: /* data-xor-offset */ + params.data_xor_offset = (size_t)atoi(optarg); + break; + case 9: /* data-xor-len */ + params.data_xor_len = (size_t)atoi(optarg); + break; + case 10: /* csum */ + if (!strcmp(optarg, "none")) + params.csum = none; + else if (!strcmp(optarg, "fix")) + params.csum = fix; + else if (!strcmp(optarg, "valid")) + params.csum = valid; + else + { + fprintf(stderr, "invalid csum parameter\n"); + exit(1); + } + break; + } + } + + if (daemon) daemonize(); + + h = NULL; + qh = NULL; + + if (*pidfile && !writepid(pidfile)) + { + fprintf(stderr, "could not write pidfile\n"); + goto exiterr; + } + + printf("opening library handle\n"); + h = nfq_open(); + if (!h) { + fprintf(stderr, "error during nfq_open()\n"); + goto exiterr; + } + + 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"); + goto exiterr; + } + + 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"); + goto exiterr; + } + + printf("binding this socket to queue '%u'\n", params.qnum); + qh = nfq_create_queue(h, params.qnum, &cb, NULL); + if (!qh) { + fprintf(stderr, "error during nfq_create_queue()\n"); + goto exiterr; + } + + 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"); + goto exiterr; + } + + if (!droproot(uid, gid)) goto exiterr; + fprintf(stderr, "Running as UID=%u GID=%u\n", getuid(), getgid()); + + fd = nfq_fd(h); + while ((rv = recv(fd, buf, sizeof(buf), 0)) && rv >= 0) + { + 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); + + return 0; + +exiterr: + if (qh) nfq_destroy_queue(qh); + if (h) nfq_close(h); + return 1; +} diff --git a/ipobfs_mod/Makefile b/ipobfs_mod/Makefile new file mode 100644 index 0000000..e932fb8 --- /dev/null +++ b/ipobfs_mod/Makefile @@ -0,0 +1,19 @@ +obj-m += ipobfs.o +KERNELDIR ?= /lib/modules/$(shell uname -r)/build +KERNELRELEASE ?= $(shell uname -r) +PWD := $(shell pwd) +DEPMOD ?= depmod +STRIP ?= strip + +all: + @$(MAKE) -C $(KERNELDIR) M=$(PWD) modules + +clean: + @$(MAKE) -C $(KERNELDIR) M=$(PWD) clean + +mod-install: + $(STRIP) --strip-debug *.ko + @$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install + $(DEPMOD) -a $(KERNELRELEASE) + +install : mod-install diff --git a/ipobfs_mod/ipobfs.c b/ipobfs_mod/ipobfs.c new file mode 100644 index 0000000..6fbb293 --- /dev/null +++ b/ipobfs_mod/ipobfs.c @@ -0,0 +1,549 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_DESCRIPTION("ip obfuscator. xor ip protocol or data payload with some values. supports multiple profiles triggered by fwmark bits"); +MODULE_AUTHOR("bol-van"); +MODULE_LICENSE("GPL"); + +#define MAX_MARK 32 + +typedef enum +{ + none=0,fix,valid +} t_csum; +static t_csum csum[MAX_MARK]; +static int ct_csum; + +static bool debug=false; +static uint mark[MAX_MARK], markmask=0; +static int ct_mark=0; +static uint data_xor[MAX_MARK]; +static int ct_data_xor=0; +static uint data_xor_offset[MAX_MARK]; +static int ct_data_xor_offset=0; +static uint data_xor_len[MAX_MARK]; +static int ct_data_xor_len=0; +static ushort ipp_xor[MAX_MARK]; +static int ct_ipp_xor=0; + +static char *prehook_s[MAX_MARK]; +static unsigned int prehook[MAX_MARK]; +static int ct_prehook; +static char *pre_s[MAX_MARK]; +static int pre[MAX_MARK]; +static int ct_pre; +static char *posthook_s[MAX_MARK]; +static unsigned int posthook[MAX_MARK]; +static int ct_posthook; +static char *post_s[MAX_MARK]; +static int post[MAX_MARK]; +static int ct_post; +static char *csum_s[MAX_MARK]; + +module_param(debug,bool,0640); + +module_param_array(mark,uint,&ct_mark,0640); +module_param(markmask,uint,0640); + +module_param_array(data_xor,uint,&ct_data_xor,0640); +module_param_array(data_xor_offset,uint,&ct_data_xor_offset,0640); +module_param_array(data_xor_len,uint,&ct_data_xor_len,0640); +module_param_array(ipp_xor,ushort,&ct_ipp_xor,0640); + +module_param_array_named(prehook,prehook_s,charp,&ct_prehook,0440); +module_param_array_named(pre,pre_s,charp,&ct_pre,0440); +module_param_array_named(posthook,posthook_s,charp,&ct_posthook,0440); +module_param_array_named(post,post_s,charp,&ct_post,0440); + +module_param_array_named(csum,csum_s,charp,&ct_csum,0440); + +MODULE_PARM_DESC(debug, "printk debug info"); +MODULE_PARM_DESC(mark, "fwmark filters : 0x100,0x200,0x400. if markmask not specified, markmask=mark for each profile"); +MODULE_PARM_DESC(markmask, "fwmark filter mask : common mask for all profiles. if not specified, markmask=mark for each profile"); +MODULE_PARM_DESC(data_xor, "uint32 data xor : 0xDEADBEAF,0x01020304,0"); +MODULE_PARM_DESC(data_xor_offset, "start xoring from position : 4,4,8"); +MODULE_PARM_DESC(data_xor_len, "xor no more than : 0,0,16"); +MODULE_PARM_DESC(ipp_xor, "xor ip protocol with : 0,0x80,42"); +MODULE_PARM_DESC(prehook, "input hook : none, prerouting (default), input, forward"); +MODULE_PARM_DESC(pre, "input hook priority : mangle (default), raw, filter or "); +MODULE_PARM_DESC(posthook, "output hook : none, postrouting (default), output, forward"); +MODULE_PARM_DESC(post, "output hook priority : mangle (default), raw, filter or "); +MODULE_PARM_DESC(csum, "csum mode : none = invalid csums are ok, fix = valid csums on original outgoing packets, valid = valid csums on obfuscated packets"); + + +#define GET_PARAM(name,idx) (idxcheck = 0; + ip->check = ip_fast_csum(ip,ip->ihl); +} + + + +static bool ip4_fragmented(struct iphdr *ip) +{ + // fragment_offset!=0 or more fragments flag + return !!(ntohs(ip->frag_off) & 0x3FFF); +} +static uint16_t ip4_frag_offset(struct iphdr *ip) +{ + return (ntohs(ip->frag_off) & 0x1FFF)<<3; +} + +static u8 ip_proto_ver(const void *net_header) +{ + return (*(u8*)net_header)>>4; +} +static u8 transport_proto(const void *net_header) +{ + switch(ip_proto_ver(net_header)) + { + case 4: + return ((struct iphdr*)net_header)->protocol; + case 6: + return ((struct ipv6hdr*)net_header)->nexthdr; + default: + return 0; + } +} + +static void fix_transport_checksum(struct sk_buff *skb) +{ + uint tlen; + u8 *pn, *pt, pver, proto; + __sum16 check=0, check_old; + + if (!skb_transport_header_was_set(skb)) return; + + pn = skb_network_header(skb); + pver = ip_proto_ver(pn); + if (pver==4 && ip4_fragmented((struct iphdr*)pn)) + { + if (debug) printk(KERN_DEBUG "ipobfs: fix_transport_checksum not fixing checksum in fragmented ip\n"); + return; // no way we can compute valid checksum for ip fragment + } + proto = transport_proto(pn); + pt = skb_transport_header(skb); + tlen = skb_headlen(skb) - (skb->transport_header - skb->network_header); + switch(proto) + { + case IPPROTO_TCP : + if (tlencheck; + ((struct tcphdr*)pt)->check = 0; + break; + case IPPROTO_UDP: + if (tlencheck; + ((struct udphdr*)pt)->check = 0; + break; + default: + return; + } + switch(pver) + { + case 4: + check = csum_tcpudp_magic(((struct iphdr*)pn)->saddr, ((struct iphdr*)pn)->daddr, tlen, proto, csum_partial(pt, tlen, 0)); + break; + case 6: + check = csum_ipv6_magic(&((struct ipv6hdr*)pn)->saddr, &((struct ipv6hdr*)pn)->daddr, tlen, proto, csum_partial(pt, tlen, 0)); + break; + } + switch(proto) + { + case IPPROTO_TCP: + ((struct tcphdr*)pt)->check = check; + break; + case IPPROTO_UDP: + ((struct udphdr*)pt)->check = check; + break; + } + if (debug) printk(KERN_DEBUG "ipobfs: fix_transport_checksum pver=%u proto=%u tlen=%u %04X => %04X\n",pver,proto,tlen,check_old,check); +} + + + +static u32 rotr32 (u32 value, uint count) +{ + return value >> count | value << (32 - count); +} +static u32 rotl32 (u32 value, uint count) +{ + return value << count | value >> (32 - count); +} +// this function can xor multi-chunked payload. data point to a chunk, len means chunk length, data_pos tells byte offset of this chunk +// on some architectures misaligned access cause exception , kernel transparently fixes it, but it costs huge slowdown - 15-20 times slower +static void modify_packet_payload(u8 *data,uint len,uint data_pos, u32 data_xor, uint data_xor_offset, uint data_xor_len) +{ + if (data_xor_offset<(data_pos+len) && (data_xor_offset+data_xor_len)>data_pos) + { + uint start=data_xor_offset>data_pos ? data_xor_offset-data_pos : 0; + if (start=8 ; len-=8,data+=8) *(u64*)data ^= nxor; + if (len>=4) + { + *(u32*)data ^= (u32)nxor; + len-=4; data+=4; + } + } + while(len--) *data++ ^= (u8)(xor=rotl32(xor,8)); + } + } +} +static void modify_skb_payload(struct sk_buff *skb,int idx,bool bOutgoing) +{ + uint len; + u8 *p,*pn,pver; + t_csum csum_mode; + + if (!skb_transport_header_was_set(skb)) return; + + len = skb_headlen(skb); + p = skb_transport_header(skb); + len -= skb->transport_header - skb->network_header; + csum_mode=GET_PARAM(csum,idx); + + // dont linearize if possible + if (skb_is_nonlinear(skb)) + { + uint last_mod_offset=GET_PARAM(data_xor_offset,idx)+GET_DATA_XOR_LEN(idx); + if(csum_mode==fix || csum_mode==valid || last_mod_offset>len) + { + if (debug) printk(KERN_DEBUG "ipobfs: nonlinear skb. skb_headlen=%u skb_data_len=%u skb_len_transport=%u last_mod_offset=%u csum_mode=%s. linearize skb",skb_headlen(skb),skb->data_len,len,last_mod_offset,string_from_csum(csum_mode)); + if (skb_linearize(skb)) + { + if (debug) printk(KERN_DEBUG "ipobfs: failed to linearize skb"); + return; + } + len = skb_headlen(skb); + p = skb_transport_header(skb); + len -= skb->transport_header - skb->network_header; + } + else + if (debug) printk(KERN_DEBUG "ipobfs: nonlinear skb. skb_headlen=%u skb_data_len=%u skb_len_transport=%u last_mod_offset=%u csum_mode=%s. dont linearize skb",skb_headlen(skb),skb->data_len,len,last_mod_offset,string_from_csum(csum_mode)); + } + + if (bOutgoing && csum_mode==fix) fix_transport_checksum(skb); + + pn = skb_network_header(skb); + pver = ip_proto_ver(pn); + modify_packet_payload(p,len,pver==4 ? ip4_frag_offset((struct iphdr*)pn) : 0, GET_PARAM(data_xor,idx), GET_PARAM(data_xor_offset,idx), GET_DATA_XOR_LEN(idx)); + + if (debug) printk(KERN_DEBUG "ipobfs: modify_skb_payload ipv%u proto=%u len=%u data_xor=%08X data_xor_offset=%u data_xor_len=%u\n",pver,transport_proto(pn),len,GET_PARAM(data_xor,idx), GET_PARAM(data_xor_offset,idx), GET_DATA_XOR_LEN(idx)); + if (csum_mode==valid) fix_transport_checksum(skb); +} + +static void modify_skb_ipp(struct sk_buff *skb,int idx) +{ + uint8_t pver,proto_old=0,proto_new=0; + switch(pver = ip_proto_ver(skb_network_header(skb))) + { + case 4: + { + struct iphdr *ip = ip_hdr(skb); + proto_old = ip->protocol; + proto_new = ip->protocol ^= (u8)GET_PARAM(ipp_xor,idx); + ip4_fix_checksum(ip); + break; + } + case 6: + { + struct ipv6hdr *ip6 = ipv6_hdr(skb); + proto_old = ip6->nexthdr; + proto_new = ip6->nexthdr ^= (u8)GET_PARAM(ipp_xor,idx); + break; + } + } + if (debug) printk(KERN_DEBUG "ipobfs: modify_skb_ipp pver=%u proto %u=>%u\n",pver,proto_old,proto_new); +} + +static uint hook_ip(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) +{ + int idx = find_mark(skb->mark); + if (idx!=-1) + { + bool bOutgoing = ((t_hook_id*)priv)->bOutgoing; + if (debug) + printk(KERN_DEBUG "ipobfs: hook_ip %s mark_idx=%d hook=%s pri=%s in=%s out=%s\n", + bOutgoing ? "out" : "in", + idx, + nf_string_from_hooknum(state->hook), + nf_string_from_priority(((t_hook_id*)priv)->priority), + state->in ? state->in->name : "null", state->out ? state->out->name : "null"); + if ((!bOutgoing && ((t_hook_id*)priv)->priority==pre[idx] && state->hook==prehook[idx]) || + (bOutgoing && ((t_hook_id*)priv)->priority==post[idx] && state->hook==posthook[idx])) + { + skb->ip_summed = CHECKSUM_UNNECESSARY; + // do data modification with original ip protocol. necessary for checksums + if (bOutgoing) + { + if (GET_PARAM(data_xor,idx)) modify_skb_payload(skb,idx,bOutgoing); + if (GET_PARAM(ipp_xor,idx)) modify_skb_ipp(skb,idx); + } + else + { + if (GET_PARAM(ipp_xor,idx)) modify_skb_ipp(skb,idx); + if (GET_PARAM(data_xor,idx)) modify_skb_payload(skb,idx,bOutgoing); + } + // clear mask bits to avoid processing in post hook + skb->mark &= ~markmask; + } + } + return NF_ACCEPT; +} + + + + + +static struct nf_hook_ops nfhk_pre[MAX_MARK*2], nfhk_post[MAX_MARK*2]; +static int ct_nfhk_pre,ct_nfhk_post; +static t_hook_id hookid_pre[MAX_MARK],hookid_post[MAX_MARK]; + +static int find_hook(const struct nf_hook_ops *nfhk, int ct, unsigned int hooknum, int priority, u8 pf) +{ + int i; + for(i=0;i>22&0x3C@0&0xFFFF=16" -j NFQUEUE --queue-num 300 --queue-bypass +iptables -t mangle -I POSTROUTING -o eth0 -p udp --sport 16 -j NFQUEUE --queue-num 300 --queue-bypass + +client ipv4 udp:16 : +iptables -t mangle -I PREROUTING -i eth0 -p 145 -m u32 --u32 "0>>22&0x3C@0>>16&0xFFFF=16" -j NFQUEUE --queue-num 300 --queue-bypass +iptables -t mangle -I POSTROUTING -o eth0 -p udp --dport 16 -j NFQUEUE --queue-num 300 --queue-bypass + +./ipobfs --qnum=300 --ipproto-xor=128 --data-xor=0x458A2ECD --data-xor-offset=4 --data-xor-len=44 + + +server ipv6 tcp:12345 : +ip6tables -t mangle -I PREROUTING -i eth0 -p 59 -m u32 --u32 "40&0xFFFF=12345" -j NFQUEUE --queue-num 300 --queue-bypass +ip6tables -t mangle -I POSTROUTING -o eth0 -p tcp --sport 12345 -j NFQUEUE --queue-num 300 --queue-bypass + +client ipv6 tcp:12345 : +ip6tables -t mangle -I PREROUTING -i eth0 -p 59 -m u32 --u32 "38&0xFFFF=12345" -j NFQUEUE --queue-num 300 --queue-bypass +ip6tables -t mangle -I POSTROUTING -o eth0 -p tcp --dport 12345 -j NFQUEUE --queue-num 300 --queue-bypass + +./ipobfs --qnum=300 --ipproto-xor=61 --data-xor=0x458A2ECD --data-xor-offset=4 + + +------------------------------------------------------------------------------------------------------------------------------------ + + +server ipv4 udp:16 : +iptables -t mangle -I PREROUTING -i eth0 -p 145 -m u32 --u32 "0>>22&0x3C@0&0xFFFF=16" -j MARK --set-xmark 0x100/0x100 +iptables -t mangle -I POSTROUTING -o eth0 -p udp --sport 16 -j MARK --set-xmark 0x100/0x100 + +client ipv4 udp:16 : +iptables -t mangle -I PREROUTING -i eth0 -p 145 -m u32 --u32 "0>>22&0x3C@0>>16&0xFFFF=16" -j MARK --set-xmark 0x100/0x100 +iptables -t mangle -I POSTROUTING -o eth0 -p udp --dport 16 -j MARK --set-xmark 0x100/0x100 + +rmmod ipobfs +insmod /lib/modules/`uname -r`/extra/ipobfs.ko mark=0x100 ipp_xor=128 data_xor=0x458A2ECD data_xor_offset=4 data_xor_len=44 + + +server ipv6 tcp:12345 : +ip6tables -t mangle -I PREROUTING -i eth0 -p 59 -m u32 --u32 "40&0xFFFF=12345" -j MARK --set-xmark 0x200/0x200 +ip6tables -t mangle -I POSTROUTING -o eth0 -p tcp --sport 12345 -j MARK --set-xmark 0x200/0x200 + +client ipv6 tcp:12345 : +ip6tables -t mangle -I PREROUTING -i eth0 -p 59 -m u32 --u32 "38&0xFFFF=12345" -j MARK --set-xmark 0x200/0x200 +ip6tables -t mangle -I POSTROUTING -o eth0 -p tcp --dport 12345 -j MARK --set-xmark 0x200/0x200 + +rmmod ipobfs +insmod /lib/modules/`uname -r`/extra/ipobfs.ko mark=0x200 ipp_xor=61 data_xor=0x458A2ECD data_xor_offset=4 diff --git a/mod_hooks.txt b/mod_hooks.txt new file mode 100644 index 0000000..f42f418 --- /dev/null +++ b/mod_hooks.txt @@ -0,0 +1,50 @@ +ipobfs_mod sets up 2 hooks : one for inbound, one for outbound + +module takes parameters + pre= (default : mangle) + prehook= (default : prerouting) + post= (default : mangle) + posthook= (default : postrouting) + +priority : mangle (default), raw, filter or + raw = NF_IP_PRI_RAW+1 + mangle = NF_IP_PRI_MANGLE+1 + filter = NF_IP_PRI_FILTER+1 + = positive or negative decimal or hex value. hex start with 0x prefix + +hooknum : prerouting (inbound default), input, output, forward, postrouting (outbound default) or + prerouting = NF_INET_PRE_ROUTING + input = NF_INET_LOCAL_IN + forward = NF_INET_FORWARD + output = NF_INET_LOCAL_OUT + postrouting = NF_INET_POST_ROUTING + = decimal or hex value. hex start with 0x prefix + +valid hooknum numbers : + +enum nf_inet_hooks { + NF_INET_PRE_ROUTING = 0, + NF_INET_LOCAL_IN = 1, + NF_INET_FORWARD = 2, + NF_INET_LOCAL_OUT = 3, + NF_INET_POST_ROUTING = 4 +}; + +priorities : + +enum nf_ip_hook_priorities { + NF_IP_PRI_FIRST = INT_MIN, + NF_IP_PRI_CONNTRACK_DEFRAG = -400, + NF_IP_PRI_RAW = -300, + NF_IP_PRI_SELINUX_FIRST = -225, + NF_IP_PRI_CONNTRACK = -200, + NF_IP_PRI_MANGLE = -150, + NF_IP_PRI_NAT_DST = -100, + NF_IP_PRI_FILTER = 0, + NF_IP_PRI_SECURITY = 50, + NF_IP_PRI_NAT_SRC = 100, + NF_IP_PRI_SELINUX_LAST = 225, + NF_IP_PRI_CONNTRACK_HELPER = 300, + NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX, + NF_IP_PRI_LAST = INT_MAX, +}; diff --git a/openwrt/package/ipobfs/Makefile b/openwrt/package/ipobfs/Makefile new file mode 100644 index 0000000..8c936f7 --- /dev/null +++ b/openwrt/package/ipobfs/Makefile @@ -0,0 +1,51 @@ +include $(TOPDIR)/rules.mk +include $(INCLUDE_DIR)/kernel.mk + +PKG_NAME:=ipobfs +PKG_RELEASE:=1 +PKG_VERSION:=1.0 + +PKG_BUILD_DIR:=$(KERNEL_BUILD_DIR)/ipobfs-$(PKG_VERSION) +export KERNELDIR:=$(LINUX_DIR) + +include $(INCLUDE_DIR)/package.mk + + +define Package/ipobfs + SECTION:=net + CATEGORY:=Network + TITLE:=ipobs + SUBMENU:=Zapret + DEPENDS:=+libnetfilter-queue +libcap +endef + +define Build/Prepare + mkdir -p $(PKG_BUILD_DIR) + $(CP) ./ipobfs ./ipobfs_mod $(PKG_BUILD_DIR)/ +endef + +define Build/Compile + $(MAKE) -C $(PKG_BUILD_DIR)/ipobfs $(TARGET_CONFIGURE_OPTS) + $(MAKE) $(KERNEL_MAKEOPTS) -C $(PKG_BUILD_DIR)/ipobfs_mod +endef + +define Package/ipobfs/install + $(INSTALL_DIR) $(1)/opt/ipobfs/ipobfs + $(INSTALL_BIN) $(PKG_BUILD_DIR)/ipobfs/ipobfs $(1)/opt/ipobfs/ipobfs +endef + +include $(INCLUDE_DIR)/kernel-defaults.mk +include $(INCLUDE_DIR)/package-defaults.mk + + +define KernelPackage/ipobfs + SECTION:=kernel + CATEGORY:=Kernel modules + SUBMENU:=Zapret + TITLE:=ipobfs kernel module + FILES:= $(PKG_BUILD_DIR)/ipobfs_mod/ipobfs.$(LINUX_KMOD_SUFFIX) +endef + + +$(eval $(call BuildPackage,ipobfs)) +$(eval $(call KernelPackage,ipobfs)) diff --git a/readme.eng.txt b/readme.eng.txt new file mode 100644 index 0000000..156cbc2 --- /dev/null +++ b/readme.eng.txt @@ -0,0 +1,271 @@ +This project is intended to fight DPI protocol analysis and bypass protocol blocking. + +One of the possible ways to overcome DPI signature analysis is to modify the protocol. +The fastest but not the easiest way is to modify the software itself. +For TCP, obfsproxy exists. However, in the case of VPN - only not very fast solutions (openvpn) work over TCP. + +What to do in case of udp? +If both endpoints are on a external IP, then its possible to modify packets on IP level. +For example, if you have a VPS, and you have an openwrt router at home and external IP from ISP, +then you can use this technique. If one endpoint is behind NAT, then abilities are limited, +but its still possible to tamper with udp/tcp headers and data payload. + +The scheme is as follows: + peer 1 <=> IP obfuscator/deobfuscator <=> network <=> IP obfuscator/deobfuscator <=> peer 2 + +In order for a packet to be delivered from peer 1 to peer 2, both having external IPs, +it is enough to have correct IP headers. You can set any protocol number, obfuscate or encrypt IP payload, +including tcp / udp headers. DPI will not understand what it is dealing with. +It will see non-standard IP protocols with unknown content. + +ipobfs +------ + +NFQUEUE queue handler, IP obfuscator/deobfuscator. + + --qnum= + --daemon ; daemonize + --pidfile= ; write pid to file + --user= ; drop root privs + --debug ; print debug info + --uid=uid[:gid] ; drop root privs + --ipproto-xor=0..255|0x00-0xFF ; xor protocol ID with given value + --data-xor=0xDEADBEAF ; xor IP payload (after IP header) with 32-bit HEX value + --data-xor-offset= ; start xoring at specified position after IP header end + --data-xor-len= ; xor block max length. xor entire packet after offset if not specified + --csum=none|fix|valid ; transport header checksum : none = dont touch, fix = ignore checksum on incoming packets, valid = always make checksum valid + +The XOR operation is symmetric, therefore the same parameters are set for the obfuscator and deobfuscator. +On each side, one instance of the program is launched. + +Filtering outgoing packets is easy because they go open, however, some u32 is required for incoming. +The protocol number ("-p") in the filter is the result of the xor of the original protocol with ipproto-xor. + +server ipv4 udp:16 : +iptables -t mangle -I PREROUTING -i eth0 -p 145 -m u32 --u32 "0>>22&0x3C@0&0xFFFF=16" -j NFQUEUE --queue-num 300 --queue-bypass +iptables -t mangle -I POSTROUTING -o eth0 -p udp --sport 16 -j NFQUEUE --queue-num 300 --queue-bypass + +client ipv4 udp:16 : +iptables -t mangle -I PREROUTING -i eth0 -p 145 -m u32 --u32 "0>>22&0x3C@0>>16&0xFFFF=16" -j NFQUEUE --queue-num 300 --queue-bypass +iptables -t mangle -I POSTROUTING -o eth0 -p udp --dport 16 -j NFQUEUE --queue-num 300 --queue-bypass + +ipobfs --qnum=300 --ipproto-xor=128 --data-xor=0x458A2ECD --data-xor-offset=4 --data-xor-len=44 + +Why data-xor-offset = 4: tcp and udp protocol headers start with source and destination port numbers, 2 bytes each. +To make it easier to write u32 do not touch the port numbers. You can touch, but then you have to figure out into what +numbers original ports will be transformed and write those values to u32. +Why data-xor-len = 44: an example is given for wireguard. 44 bytes is enough to XOR the udp header and all wireguard headers. +Next come the encrypted wireguard data, it makes no sense to XOR it. + +You can even turn udp into "tcp trash" with ipproto-xor = 23. According to the ip header, this is tcp, but in place of the tcp header is garbage. +On the one hand, such packets can go through middle-boxes, and conntrack can go crazy. +On the other hand, it may even be good. + +There are nuances with ipv6. In ipv6 there is no concept of a protocol number. But there is the concept of "next header". +As in ipv4, you can write anything there. But in practice, this can cause ICMPv6 Type 4 - Parameter Problem messages. +To avoid this, you can cast the protocol to the value 59. It means "no Next Header". +To get "ipproto-xor" parameter, XOR original protocol number with 59. + +udp : ipproto-xor=17^59=42 +tcp : ipproto-xor=6^59=61 + +server ipv6 tcp:12345 : +ip6tables -t mangle -I PREROUTING -i eth0 -p 59 -m u32 --u32 "40&0xFFFF=12345" -j NFQUEUE --queue-num 300 --queue-bypass +ip6tables -t mangle -I POSTROUTING -o eth0 -p tcp --sport 12345 -j NFQUEUE --queue-num 300 --queue-bypass + +client ipv6 tcp:12345 : +ip6tables -t mangle -I PREROUTING -i eth0 -p 59 -m u32 --u32 "38&0xFFFF=12345" -j NFQUEUE --queue-num 300 --queue-bypass +ip6tables -t mangle -I POSTROUTING -o eth0 -p tcp --dport 12345 -j NFQUEUE --queue-num 300 --queue-bypass + +ipobfs --qnum=300 --ipproto-xor=61 --data-xor=0x458A2ECD --data-xor-offset=4 + +IP FRAGMENTATION +If the sending host sends too long packet, it is fragmented at the IP level. +The receiving host only reassembles packets addressed to the host itself. +In the PREROUTING chain packets are still fragmented. +When applying deobfuscation only to a part of the packet, the cheksum inevitably becomes invalid. +csum = fix does not help. +For ipv4, adding a rule to the INPUT chain instead of PREROUTING helps. +Of course, only packets addressed to the host itself are caught, but they come +in NFQEUEUE in already assembled state and correctly deobfuscated. +IP fragmentation is an undesirable, it should be combated by setting the correct MTU +inside the tunnel. There are some protocols that rely on ip fragmentation. These include IKE (without rfc7383). + +IPV6 FRAGMENTATION +Fragmentation is also possible in ipv6, however, it is performed only by the sending host, usually only for +udp and icmp when the frame does not fit into mtu. The header "44" is added to all fragments immediately after the ipv6 header. +Unfortunately, all attempts to catch the reconstructed full frame in various tables failed. +Only the first fragment is caught. It was not possible to find out the reason. Is this a bug or feature is known only to Torvalds. + +CHECKSUMS : +Work with checksums begins when a tcp or udp packet passes through the obfuscator. +For incoming packets, the ipproto-xor operation performed first, and after that it is analyzed whether it is tcp or udp. +For outgoing, the opposite is true. +--csum=none - do not touch checksums at all. if after deobfuscation checksum is invalid, the system will discard the packet. +--csum=fix - checksum ignore mode. its not possible to disable checksum verification inside NFQUEUE. +Instead, on incoming packets checksum is recomputed and replaced, so the system will accept the packet. +--csum=valid - bring the checksum to a valid state for all packets - incoming and outgoing. +This mode is useful when working through NAT which blocks invalid packets. + +Recomputing checksum increases cpu usage. +See also section "NAT break". + + +DISADVANTAGE : +Each packet will be thrown into nfqueue, therefore the speed will decrease significantly. 2-3 times. +If you compare wireguard + ipobfs with openvpn on a soho router, then openvpn will still be slower. + + +ipobfs_mod +----------- + +The same as ipobfs, but implemented as a linux kernel module. It gives a performance drop of only 20%. +It duplicates ipobfs logic and is compatible with it. + +Its possible to use ipobfs on peer1 and ipobfs_mod on peer2, they will work together. +However, by default ipobfs_mod will produce tcp and udp packets with invalid cheksums, the system +with ipobfs will discarded them. Use csum=fix on ipobfs_mod side. + +The iptables commands are the same, but instead of "-j NFQEUEUE" use "-j MARK --set-xmark". +ipobfs_mod performs packet processing based on fwmark. + +Settings are passed through the kernel module parameters specified in the insmod command. + +server ipv4 udp:16 : +iptables -t mangle -I PREROUTING -i eth0 -p 145 -m u32 --u32 "0>>22&0x3C@0&0xFFFF=16" -j MARK --set-xmark 0x100/0x100 +iptables -t mangle -I POSTROUTING -o eth0 -p udp --sport 16 -j MARK --set-xmark 0x100/0x100 + +client ipv4 udp:16 : +iptables -t mangle -I PREROUTING -i eth0 -p 145 -m u32 --u32 "0>>22&0x3C@0>>16&0xFFFF=16" -j MARK --set-xmark 0x100/0x100 +iptables -t mangle -I POSTROUTING -o eth0 -p udp --dport 16 -j MARK --set-xmark 0x100/0x100 + +rmmod ipobfs +insmod /lib/modules/`uname -r`/extra/ipobfs.ko mark=0x100 ipp_xor=128 data_xor=0x458A2ECD data_xor_offset=4 data_xor_len=44 + +The module supports up to 32 profiles. Parameter settings for each profile are separated by commas. +For example, the following command combines the functions of 2 NFQUEUE handlers from the previous examples: +insmod /lib/modules/`uname -r`/extra/ipobfs.ko mark=0x100,0x200 ipp_xor=128,61 data_xor=0x458A2ECD,0x458A2ECD data_xor_offset=4,4 data_xor_len=44,0 +It is possible to use different profiles for outgoing and incoming packets. +This will confuse DPI even more by reducing the correlation of in/out streams. +If parameter 'markmask' is set, profile with mask/markmask wins, otherwise mask/mask is searched. +markmask parameter is single for all profiles, no need for commas. +Use markmask if profiles are numerous to not waste single bit for each one. +For example : 0x10/0xf0, 0x20/0xf0, ..., 0xf0/0xf0 + +By default, the module sets a hook on incoming packets with priority mangle+1, so that the table mangle was already processed +by the time of the call. If non-standard IP protocols arrive at the input, everything is OK. But if there are packets with +the transport protocol that support checksumming, such as tcp or udp, then modified packets with invalid checksum +will not reach the mangle+1 hook. The module will not receive them. +To solve this problem, specify the pre=raw parameter and do : iptables -t raw -I PREROUTING ... +Outgoing packets can be processed in the usual manner through mangle. + +If you need to work with fragmented ipv4 protocols, replace iptables PREROUTING with INPUT (see the remark in the ipobfs section), +specify the module parameter "prehook=input". + +Parameters pre,prehook,post,posthook are set individually for each profile and must be comma separated. + +The module disables OS-level checksum checking and computing for all processed packets, in some cases +recomputing tcp and udp checksums independently. +If the parameter csum=none, module does not compute checksum at all, allowing sending packets with invalid checksum +before obfuscation. Deobfuscated packets can contain invalid checksum. +If csum=fix, the module takes over the recalculation of the checksum on outgoing packets before the payload is modified, +thereby repeating the functions of the OS or hardware offload. Otherwise OS or hw offload would spoil 2 bytes of data +and after deobfuscation packet would contain incorrect checksum. +If csum=valid, the recalculation of the checksum is done after modifying the payload for both outgoing and incoming packets. +This ensures the visibility of the transmission of packets with a valid checksum. +Checksum correction on the incoming packet is necessary if the device with ipobfs is not the receiver, +but performs the function of a router (forward). So that there is a valid packet on the output interface. +The regular recipient will not accept packets with incorrect checksum. + +The debug = 1 parameter enables debugging output. You will see what is done with each processed packet in dmesg. +It should be used only for debugging. With a large number of packets, the system will slow down significantly +due to excessive output in dmesg. + +You can view and change some ipobfs parameters without reloading the module : /sys/module/ipobfs/parameters + +COMPILING MODULE on traditional linux system : +At first install kernel headers. for debian : +sudo apt-get install linux-headers..... +cd ipobfs_mod +make +sudo make install + +SPEED NOTICE +If only ipproto-xor is specified, slowdown is very close to zero. +With data-xor its preferred not to xor offsets after 100-140 bytes. +This way you can avoid linearizing skb's and save lots of cpu time. +debug=1 option can show whether linearizing happens. + +openwrt +------- + +On a x64 linux system, download and unzip the SDK from your firmware version for your device. +The SDK version must exactly match the firmware version, otherwise you will not build a suitable kernel module. +If you built the firmware yourself, instead of the SDK, you can and should use that buildroot. +scripts/feeds update -a +scripts/feeds install -a +Copy openwrt/* to SDK folder, preserving directory structure. +Copy ipobfs и ipobfs_mod (source code) to packages/ipobfs (the one there openwrt Makefile is). +From SDK root run : make package/ipobfs/compile V=99 +Look for 2 ipk : bin/packages/..../ipobfs..ipk и bin/targets/..../kmod-ipobfs..ipk +Copy selected version to the device, install via "opkg install ...ipk". +If reinstalling, first "opkg remove ipobfs" / "opkg remove kmod-ipobfs". + +NAT break +------------ + +In the general case, its safe to assume that NAT can only pass tcp, udp, icmp traffic. +Some NATs also contain helpers for special protocols (GRE). But not all NATs and not on all devices. +NAT can pass non-standard IP protocols, but it does not have the means to track the source IP that initiated +communication. If non-standard protocols work through NAT, then only work for only one device behind NAT. +Using one IP protocol with more than one device behind NAT is not possible. There will be a conflict. +Therefore, ipproto-xor can be used, but carefully. + +Consider linux-based NAT (almost all home routers) without helpers. +As the study shows, transport header fields containing payload length and flags are important. +Therefore, the minimum xor-data-offset for tcp is 14, for udp it is 6. Otherwise, the packet will not pass NAT at all. + +Any NAT will definitely follow the tcp flags, because conntrack determines the start of the connection. +Conntrack is vital part of any NAT. Flags field offset in tcp header is 13. + +Linux conntrack by default verifies transport protocol checksums and does not track packets with invalid checksum. +Such packets do not cause the appearance or change of entries in the conntrack table, the status of packets is INVALID, +SNAT operation will not be applied to them, nevertheless, the forwarding of such packets will still happen unchanged, +maintaining the source address from the internal network. To avoid this behavior, properly configured routers apply +rules like "-m state --state INVALID -j DROP" or "-m conntrack --ctstate INVALID -j DROP", thereby prohibiting forwarding +packets that conntrack refused to account. +This behavior can be changed with the command "sysctl -w net.netfilter.nf_conntrack_checksum=0". +In this case, the checksums will not be considered, conntrack will accept packets even with invalid cheksums, NAT will work. +In openwrt, by default net.netfilter.nf_conntrack_checksum=0, so NAT works with invalid packets. +But other routers usually do not change the default value, which is 1. + +Without exception, all NATs will correct the 2-byte checksum in tcp (offset 18) and udp (offset 6) header, +since it is computed using ip source and destination. NAT changes the source ip when sending, source port +can also change. To save resources, a full checksum recalculation is usually not performed. +The initial checksum is taken as a basis, the difference between the initial and changed values​is added to it. +The recipient receives a packet with an invalid checksum, then packet is deobfuscated by ipobfs and checksum becomes +valid again, but only if the initial checksum was not changed during obfuscation, that is, +data-xor-offset> = 20 for tcp and data-xor-offset> = 8 for udp. +The obfuscator XORs, checksum is additive, so they are incompatible. +ipobfs by default does not recalculate the checksums of transport headers, so if it is used at the receiving end, then +data-xor-offset must not cover checksum field, otherwise the packet will be discarded by the system after deobfuscation +As an alternative use --csum=fix option. +ipobfs_mod disables checksums verification, so there is no such problem when using it. default behavior is similar to --csum=fix +If ipproto_xor is used, router will not recalculate the checksum, packet will arrive with invalid checksum after deobfuscation. + +Many routers perform mss fix (-j TCPMSS --clamp-mss-to-pmtu or -j TCPMSS --set-mss). +mss is in the tcp header options. Windows and linux send mss as the first option. The option itself takes 4 bytes. +It turns out that the minimum xor-data-offset for tcp rises to 24, because bytes 22-23 can be changed by router. + +SUMMARY : + tcp : data-xor-offset>=24 + udp : data-xor-offset>=8 + +If NAT doesn’t pass packets with invalid checksums, use --csum=valid option. +In terms of cpu load, it would be preferable not to use the --csum=valid mode if possible. + +There is information that some mobile operators terminate tcp on their servers for later proxying to the original +destination. In this case, any tcp modification not at the data flow level is doomed to failure. +A terminating middlebox will reject packets with a corrupted header or invalid checksum. +An outgoing connection from middlebox will not repeat the same packetization as the original connection. +Use obfsproxy. diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..549b527 --- /dev/null +++ b/readme.txt @@ -0,0 +1,279 @@ +English +------- + +see readme.eng.txt + +Для чего это нужно +------------------ + +На нас надвигается эпоха, когда повсеместно могут начать блокировать уже не отдельные IP адреса или домены, а протоколы. +Одним из способов возможного преодоления сигнатурного анализа на DPI является модификация протокола. +Лучший способ - модифицировать сам софт, работающий с этими протоколами. Но не всегда это бывает просто или возможно. +Для TCP существует obfsproxy. Однако, в случае VPN - по TCP работают только не очень быстрые решения (openvpn). + +Что же делать в случае udp ? +Если оба endpoint-а находятся на белом IP, то можно как следует поизвращаться прямо на уровне IP и дальше. +Например, если у вас VPS, а дома роутер на openwrt, вы имеете прямой IP от провайдера, то можно применить эту технику. +Если один endpoint находится за NAT, то можно как следует поизвращаться на уровне udp или tcp, +не трогая IP и начало заголовков tcp/udp. + +Схема выглядит следующим образом : + peer 1 <=> обфускатор/деобфускатор IP <=> сеть <=> обфускатор/деобфускатор IP <=> peer 2 + +Чтобы пакет был доставлен от peer 1 до peer 2, оба имеющих белые IP, достаточно лишь иметь корректные IP заголовки. +Можно выставить любой protocol number, проксорить или зашифровать IP payload, включая tcp/udp хедеры. +DPI от такого может выпасть в осадок. Оно не будет понимать с чем имеет дело. +Какие-то нестандартные IP протоколы с непонятно каким наполнением. +Конечно, DPI может обрезать весь мусор (по его мнению), но совсем не обязательно будет это делать. +Обычная программа не может сгененировать подобного типа "мусор", если только не использует raw sockets. +Значит разработчики DPI вряд ли поставят себе целью ловить рыбку в "мутной воде", если только какое-то решение +по обходу DPI на базе этой техники не станет достаточно популярным или белосписочность фильтров не достигнет высокого уровня. + + +ipobfs +------ + +Обработчик очереди NFQUEUE, обфускатор/деобфускатор пакетов. + + --daemon ; демонизировать прогу + --pidfile= ; сохранить PID в файл + --user= ; менять uid процесса + --uid=uid[:gid] ; менять uid процесса + --qnum=200 ; номер очереди + --debug ; вывод отладочной информации + --ipproto-xor=0..255|0x00..0xFF; проксорить protocol number с указанным значением + --data-xor=0xDEADBEAF ; проксорить содержимое IP payload указанным 32-битным HEX значением + --data-xor-offset= ; начинать проксоривание со смещения position после IP хедера + --data-xor-len= ; ксорить не более указанного количества байтов, начиная с позиции data-xor-offset + --csum=none|fix|valid ; режим работы с чексуммой транспортных хедеров tcp и udp + +Операция xor симметрична, поэтому для обфускатора и деобфускатора задаются одни и те же параметры. +На каждой стороне запускается по одному экземпляру программы. + +Придется немного повозиться с iptables. Отфильтровать исходящий пакет просто, потому что он +идет "в открытую". На входящий пакет придется писать фильтры через u32. +Номер протокола ("-p") в фильтре - это результат xor исходного протокола с ipproto-xor. + +server ipv4 udp:16 : +iptables -t mangle -I PREROUTING -i eth0 -p 145 -m u32 --u32 "0>>22&0x3C@0&0xFFFF=16" -j NFQUEUE --queue-num 300 --queue-bypass +iptables -t mangle -I POSTROUTING -o eth0 -p udp --sport 16 -j NFQUEUE --queue-num 300 --queue-bypass + +client ipv4 udp:16 : +iptables -t mangle -I PREROUTING -i eth0 -p 145 -m u32 --u32 "0>>22&0x3C@0>>16&0xFFFF=16" -j NFQUEUE --queue-num 300 --queue-bypass +iptables -t mangle -I POSTROUTING -o eth0 -p udp --dport 16 -j NFQUEUE --queue-num 300 --queue-bypass + +ipobfs --qnum=300 --ipproto-xor=128 --data-xor=0x458A2ECD --data-xor-offset=4 --data-xor-len=44 + +Почему data-xor-offset=4 : у tcp и udp в начале загловка идут номера порта источника и приемника, по 2 байта на каждый. +Чтобы проще было писать u32 не трогаем номера портов. Можно и тронуть, но тогда придется вычислить что же получится +после проксоривания и писать в u32 уже эти значения. +Почему data-xor-len=44 : пример приведен для wireguard. 44 байта достаточно, чтобы заксорить udp header и все заголовки wireguard. +Дальше идут шифрованные данные wireguard, их ксорить смысла нет. + +Можно даже превратить udp в "tcp мусор" при ipproto-xor=23. Согласно заголовку ip это tcp, но на месте tcp хедера мусор. +Такого рода пакеты с одной стороны могут нарваться на middle-боксы, и на них сойдет с ума conntrack. +С другой стороны это может оказаться даже хорошо. + +С ipv6 есть нюансы. В ipv6 нет понятия номера протокола. Зато есть понятие "next header". +Как и в ipv4 можно туда записать все что угодно. Но на практике это может вызвать лавину ICMPv6 собщений "Type 4 - Parameter Problem". +Чтобы этого избежать, надо приводить протокол к значению 59. Оно означает "no Next Header". +Проксорьте 59 с номером исходного протокола, получите параметр для "ipproto-xor". +Например, для udp номер протокола - 17. ipproto-xor=17^59=42 +Для tcp номер протокола - 6. ipproto-xor=6^59=61 + +server ipv6 tcp:12345 : +ip6tables -t mangle -I PREROUTING -i eth0 -p 59 -m u32 --u32 "40&0xFFFF=12345" -j NFQUEUE --queue-num 300 --queue-bypass +ip6tables -t mangle -I POSTROUTING -o eth0 -p tcp --sport 12345 -j NFQUEUE --queue-num 300 --queue-bypass + +client ipv6 tcp:12345 : +ip6tables -t mangle -I PREROUTING -i eth0 -p 59 -m u32 --u32 "38&0xFFFF=12345" -j NFQUEUE --queue-num 300 --queue-bypass +ip6tables -t mangle -I POSTROUTING -o eth0 -p tcp --dport 12345 -j NFQUEUE --queue-num 300 --queue-bypass + +ipobfs --qnum=300 --ipproto-xor=61 --data-xor=0x458A2ECD --data-xor-offset=4 + + +IP ФРАГМЕНТАЦИЯ +Если отсылающий хост посылает слишком длинный пакет, он фрагментируется на уровне IP. +Принимающий хост собирает пакеты только адресованные самому хосту. +В цепочке PREROUTING идут еще фрагментированные пакеты. При применении деобфускации +только к части пакета чексумма неизбежно оказывается инвалидной. csum=fix не помогает. +Для ipv4 помогает добавление правила в цепочку INPUT вместо PREROUTING. +Само собой, так ловятся только пакеты, адресованные самому хосту, зато они приходят +в NFQEUEUE уже собранными и корректно деобфусцируются. +Фрагментация IP - нежелательное явление, с ней следует бороться выставлением корректного mtu +внутри тоннеля. Есть некоторые протоколы, которые расчитывают на ip фрагментацию. К ним относится IKE (без rfc7383). + +IPV6 ФРАГМЕНТАЦИЯ +В ipv6 тоже возможна фрагментация, однако она выполняется только отсылающим хостом, как правило только для +udp и icmp, когда фрейм не влезает в mtu. Ко всем фрагментам сразу после ipv6 хедера добавляется хедер "44". +К сожалению, все попытки поймать реконструированный полный фрейм в различных таблицах не приводят к успеху. +Ловится только первый фрагмент. Причину так и не удалось выяснить. Баг это или фича известно разве что Торвальдсу. +Так что лучший совет - не допускать фрагментации вовсе. + +ЧЕКСУММЫ : +Работа с чексуммами начинается, когда через обфускатор проходит пакет tcp или udp. +Для входящих пакетов сначала выполняется операция ipproto-xor, и уже после нее анализируется получился ли пакет tcp или udp. +Для исходящих - наоборот. +--csum=none - не трогать чексуммы вообще. если после деобфускации чексумма окажется инвалидной, система отбросит пакет. +--csum=fix - режим игнорирования чексуммы. технически невозможно в NFQUEUE отключить проверку чексумм, потому чексумма + приводится к валидному значению, чтобы система не отбросила пакет. работает только для входящих пакетов. +--csum=valid - приводить чексумму к валидному состоянию для всех пакетов - входящих и исходящих. Режим полезен при работе через NAT, +не пропускающие пакеты с инвалидной чексуммой. +Пересчет чексумм увеличивает нагрузку на cpu. +См. так же раздел "пробитие NAT". + +НЕДОСТАТКИ : +Каждый пакет будет забрасываться в nfqueue, потому скорость значительно снизится. В 2-3 раза. +Если сравнивать wireguard+ipobfs с openvpn на soho роутере, то openvpn все равно окажется медленней. + + +ipobfs_mod +----------- + +То же самое, что и ipobfs, только выполнен в виде модуля ядра linux. Дает просадку производительности всего в пределах 20%. + +По логике функционирования дублирует ipobfs и совместим с ним. +Значит можно на 1 хосте включить ipobfs, на другом ipobfs_mod, и они вместе будут работать. +Однако, по умолчанию ipobfs_mod будет продуцировать tcp и udp пакеты с инвалидной чексуммой, система +с ipobfs их будет отбрасывать. Используйте csum=fix на стороне ipobfs_mod. + +Команды iptables те же самые, только вместо направления на очередь NFQEUEUE выставляется бит в fmwark. +ipobfs_mod реагирует на выставленные биты и производит обработку пакетов. + +Настройки передаются через параметры модуля ядра, задаваемые командой insmod. + +server ipv4 udp:16 : +iptables -t mangle -I PREROUTING -i eth0 -p 145 -m u32 --u32 "0>>22&0x3C@0&0xFFFF=16" -j MARK --set-xmark 0x100/0x100 +iptables -t mangle -I POSTROUTING -o eth0 -p udp --sport 16 -j MARK --set-xmark 0x100/0x100 + +client ipv4 udp:16 : +iptables -t mangle -I PREROUTING -i eth0 -p 145 -m u32 --u32 "0>>22&0x3C@0>>16&0xFFFF=16" -j MARK --set-xmark 0x100/0x100 +iptables -t mangle -I POSTROUTING -o eth0 -p udp --dport 16 -j MARK --set-xmark 0x100/0x100 + +rmmod ipobfs +insmod /lib/modules/`uname -r`/extra/ipobfs.ko mark=0x100 ipp_xor=128 data_xor=0x458A2ECD data_xor_offset=4 data_xor_len=44 + +Модуль поддерживает до 32 профилей. Настройки параметров для каждого профиля идут через запятую. +Например, следующая команда объединит функции 2 обработчиков NFQUEUE из предыдущих примеров : +insmod /lib/modules/`uname -r`/extra/ipobfs.ko mark=0x100,0x200 ipp_xor=128,61 data_xor=0x458A2ECD,0x458A2ECD data_xor_offset=4,4 data_xor_len=44,0 +Возможно применение разных профилей для исходящих и входящих пакетов. Так вы еще больше запутаете DPI, уменьшив корреляцию in/out потоков. +Если задан параметр markmask, то профиль ищется по маске mask/markmask, в противном случае по маске mark/mark. +markmask - один на все профили, через запятую перечилять для каждого профиля не нужно. +Используйте markmask, когда у вас много профилей, чтобы не расходовать по биту на каждый. +Например, следующий вариант может вместить 15 профилей в 4 бита : 0x10/0xf0, 0x20/0xf0, ..., 0xf0/0xf0 +0x00/0xf0 означает, что пакет не обрабатывается модулем. + +По умолчанию модуль устанавливает хук на входящие пакеты с приоритетом mangle+1, чтобы к моменту вызова была выполнена таблица mangle. +Если на вход поступают нестандартные протоколы, все в порядке. Но если идут пакеты с транспортным протоколом, в которых предусмотрена +чексумма, такие как tcp или udp, то модифицированные пакеты с инвалидной чексуммой до хука mangle+1 не доходят. Модуль их не получает. +Чтобы решить эту проблему, укажите параметр pre=raw и делайте : iptables -t raw -I PREROUTING ... +Исходящие пакеты можно обрабатывать в обычном порядке через mangle. + +Если необходимо работать с фрагментированными ipv4 протоколами, замените в iptables PREROUTING на INPUT (см замечание в разделе ipobfs), +укажите параметр модуля "prehook=input". + +Параметры pre,prehook,post,posthook задаются индивидуально для каждого профиля, поэтому если профилей несколько, их нужно перечислять +через запятую. + +Модуль отключает проверку и подсчет чексумм на уровне ОС для обрабатываемых пакетов, в некоторых случаях пересчитывая +чексуммы tcp и udp самостоятельно. +Если параметр csum=none, модуль не считает суммы вообще. Сумма исходящего пакета может оказаться невалидной. +При csum=fix модуль считает сумму исходящего пакета до модификации пейлоада, тем самым повторяя функции ОС +или offload-а на сетевой карточке, чтобы они не сделали это на уже модифицированном пейлоаде и не испортили 2 байта данных, +а передаваемый пакет после деобфускации содержал корректную чексумму. +Если csum=valid, пересчет чексуммы производится после модификации пейлоада, как для исходящих, так и для входящих пакетов. +Тем самым обеспечивается видимость передачи пакетов с валидной чексуммой. Коррекция чексуммы на входящем пакете необходима, +если устройство с ipobfs не является получателем пакета, а выполняет функцию роутера (forward). Чтобы на выходном интерфейсе был валидный пакет. +Обычный получатель не примет пакеты с инвалидной чексуммой. + +Параметр debug=1 включает вывод отладочной информации. Вы увидите что делается с каждым обрабатываемым пакетом в dmesg. +Его стоит применять только для отладки. При большом количестве пакетов система сильно затормозится из-за избыточного вывода в dmesg. + +Посмотреть и изменить некоторые параметры ipobfs можно без перезагрузки модуля : /sys/module/ipobfs/parameters + +СБОРКА МОДУЛЯ ЯДРА на традиционной linux системе : +установить заголовки ядра. для debian : +sudo apt-get install linux-headers..... +cd ipobfs_mod +make +sudo make install + +ЗАМЕЧАНИЕ ПО СКОРОСТИ : +Если задан только ipproto-xor, замеделение можно считать нулевым. Выполняется простейшая операция изменения 1 байта. +Тем не менее даже такой трюк может помочь обойти dpi. +Если используется data-xor, то старайтесь ксорить только начало пакета. Если ксорение не выходит за пределы ~100-140 байт, +скорее всего не придется выполнять ресурсоемкую операцию skb_linearize. Именно на ней тратится больше всего ресурсов cpu. +С debug=1 можно проверить выполняется ли linearize. + +openwrt +------- + +На системе linux скачайте и распакуйте SDK от вашей версии прошивки для вашего девайса. +Версия SDK должна в точности соответствовать версии прошивки, иначе вы не соберете подходящий модуль ядра. +Если вы собирали прошивку самостоятельно, вместо SDK можно и нужно использовать этот buildroot. +scripts/feeds update -a +scripts/feeds install -a +Скопируйте openwrt/* в SDK, сохраняя структуру директорий. +В packages/ipobfs, там где Makefile, поместите каталоги ipobfs и ipobfs_mod с исходниками. +Из корня SDK : make package/ipobfs/compile V=99 +Должны получиться 2 ipk : bin/packages/..../ipobfs..ipk и bin/targets/..../kmod-ipobfs..ipk +Переносите их на девайс, устанавливаете через "opkg install ...ipk". Устанавливать можно только то, что вам нужно : ipobfs или kmod-ipobfs. + + +Пробитие NAT +------------ + +В общем случае можно утверждать, что NAT способны пропускать лишь трафик tcp, udp, icmp. ++ некоторые NAT еще содержат хелперы для пропуска особых протоколов (GRE). Но не все и не на всех устройствах. +NAT может пропускать нестандартные IP протоколы, но у него нет средств отследить исходный IP, который инициировал +отсылку. Если нестандартные протоколы и будут работать через NAT, то только для одного устройства за NAT. +Использование одного IP протокола более чем одним устройством за NAT невозможно. Будет конфликт. +Учитывая этот факт, ipproto-xor применять можно, но осторожно. + +Рассмотрим NAT на базе linux (почти все домашние роутеры) без хелперов. +Как показывает исследование, для него важны поля транспортного хедера, содержащие длину пейлоада и флаги. +Поэтому, минимальный xor-data-offset для tcp - 14 , для udp - 6. Иначе пакет вовсе не пройдет NAT. + +Любой NAT обязательно будет следить за флагами tcp, ведь по ним conntrack определяет начало установки соединения. +Без conntrack не работает ни один NAT. Смещение флагов в tcp header - 13. + +Linux conntrack по умолчанию проверяет чексуммы транспортных протоколов и не учитывает пакеты с инвалидной чексуммой. +Такие пакеты не вызывают появление или изменение записей в таблице conntrack, статус пакетов - INVALID, операция SNAT +к ним применена не будет, тем не менее форвардинг пакета все равно случится в неизменном виде, с сохранением адреса источника +из внутренней сети. Чтобы этого избежать, правильно настроенные роутеры применяет правила вида +"-m state --state INVALID -j DROP" или "-m conntrack --ctstate INVALID -j DROP", тем самым запрещая форвардинг +пакетов, от которых отказался conntrack. +Поведение можно изменить командой "sysctl -w net.netfilter.nf_conntrack_checksum=0". В этом случае чексуммы считаться не будут, +conntrack будет принимать пакеты даже с инвалидной чексуммой, NAT работать будет. +В openwrt по умолчанию net.netfilter.nf_conntrack_checksum=0, так что система пропускает инвалидные пакеты. +Но остальные роутеры как правило не меняют значение по умолчанию, которое =1. + +Все без исключения NAT будут исправлять 2-байтовую чексумму в tcp (смещение 18) и udp (смещение 6), +поскольку она считается с учетом ip источника и назначения. NAT меняет ip источника при отсылке, при невозможности +сохранить исходный source port меняется и он. Для экономии ресурсов полный пересчет суммы обычно не производится. +За основу берется исходная сумма, к ней добавляется разница между исходными и измененными значениями. +К получателю приходит пакет с инвалидной суммой, далее он деобфусцируется средствами ipobfs и сумма становится валидной, +если при обфускации не была затронута исходная сумма, то есть data-xor-offset>=20 для tcp и data-xor-offset>=8 для udp. +Обфускатор работает по xor, checksum считается сложением, поэтому они несовместимы. +ipobfs по умолчанию не пересчитывает чексуммы транспортных хедеров, поэтому если на принимающем конце используется он, то +data-xor-offset необходимо делать таким, чтобы чексумма не затрагивалась, иначе пакет будет отброшен системой после деобфускации. +альтернатива - использовать опцию --csum=fix +ipobfs_mod отключает проверку чексумм, поэтому при его использовании этой проблемы нет. поведение по умолчанию аналогично --csum=fix +Если применяется ipproto_xor, то роутер не будет пересчитывать суммы, пакет придет с инвалидной суммой после деобфускации. + +Большинство роутеров выполняют mss fix ( -j TCPMSS --clamp-mss-to-pmtu или -j TCPMSS --set-mss ). +mss находится в опциях tcp header. Windows и linux шлют mss первой опцией. Сама опция занимает 4 байта. +Выходит, минимальный xor-data-offset для tcp поднимается до 24, чтобы не трогать mss, который попортит роутер. + +ИТОГ : + tcp : data-xor-offset>=24 + udp : data-xor-offset>=8 + +Если NAT не пропускает пакеты с инвалидной чексуммой, используйте настройку --csum=valid. +С точки зрения нагрузки на cpu предпочительней будет не использовать режим --csum=valid, если NAT пропускает пакеты с инвалидной чексуммой. + +Есть информация, что некоторые мобильные операторы производят терминацию tcp на своих серверах для последующего +проксирования к точке исходного назначения. В этом случае любая модификация tcp не на уровне потока данных обречена на провал. +Терминирующий middlebox отвергнет пакеты с испорченным заголовком или неверной чексуммой. +Исходящее соединение от middlebox не будет повторять такое же разбиение на пакеты, как исходное соединение. +Пользуйтесь obfsproxy.