├── ipobfs ├── Makefile ├── checksum.c ├── checksum.h └── ipobfs.c ├── ipobfs_mod ├── Makefile └── ipobfs.c ├── iptables.txt ├── mod_hooks.txt ├── openwrt └── package │ └── ipobfs │ └── Makefile ├── readme.eng.txt └── readme.txt /ipobfs/Makefile: -------------------------------------------------------------------------------- 1 | CC ?= gcc 2 | CFLAGS ?= -s -O3 3 | LIBS = -lnetfilter_queue -lnfnetlink -lcap 4 | SRC_FILES = *.c 5 | 6 | all: ipobfs 7 | 8 | ipobfs: $(SRC_FILES) 9 | $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(LIBS) 10 | 11 | clean: 12 | rm -f ipobfs *.o 13 | -------------------------------------------------------------------------------- /ipobfs/checksum.c: -------------------------------------------------------------------------------- 1 | #include "checksum.h" 2 | #include 3 | 4 | //#define htonll(x) ((1==htonl(1)) ? (x) : ((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32)) 5 | //#define ntohll(x) ((1==ntohl(1)) ? (x) : ((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32)) 6 | 7 | static uint16_t from64to16(uint64_t x) 8 | { 9 | uint32_t u = (uint32_t)(uint16_t)x + (uint16_t)(x>>16) + (uint16_t)(x>>32) + (uint16_t)(x>>48); 10 | return (uint16_t)u + (uint16_t)(u>>16); 11 | } 12 | 13 | static uint16_t do_csum(const uint8_t * buff, size_t len) 14 | { 15 | uint8_t odd; 16 | size_t count; 17 | uint64_t result,w,carry=0; 18 | uint16_t u16; 19 | 20 | if (len <= 0) return 0; 21 | odd = (uint8_t)(1 & (size_t)buff); 22 | if (odd) 23 | { 24 | // any endian compatible 25 | u16 = 0; 26 | *((uint8_t*)&u16+1) = *buff; 27 | result = u16; 28 | len--; 29 | buff++; 30 | } 31 | else 32 | result = 0; 33 | count = len >> 1; /* nr of 16-bit words.. */ 34 | if (count) 35 | { 36 | if (2 & (size_t) buff) 37 | { 38 | result += *(uint16_t *) buff; 39 | count--; 40 | len -= 2; 41 | buff += 2; 42 | } 43 | count >>= 1; /* nr of 32-bit words.. */ 44 | if (count) 45 | { 46 | if (4 & (size_t) buff) 47 | { 48 | result += *(uint32_t *) buff; 49 | count--; 50 | len -= 4; 51 | buff += 4; 52 | } 53 | count >>= 1; /* nr of 64-bit words.. */ 54 | if (count) 55 | { 56 | do 57 | { 58 | w = *(uint64_t *) buff; 59 | count--; 60 | buff += 8; 61 | result += carry; 62 | result += w; 63 | carry = (w > result); 64 | } while (count); 65 | result += carry; 66 | result = (result & 0xffffffff) + (result >> 32); 67 | } 68 | if (len & 4) 69 | { 70 | result += *(uint32_t *) buff; 71 | buff += 4; 72 | } 73 | } 74 | if (len & 2) 75 | { 76 | result += *(uint16_t *) buff; 77 | buff += 2; 78 | } 79 | } 80 | if (len & 1) 81 | { 82 | // any endian compatible 83 | u16 = 0; 84 | *(uint8_t*)&u16 = *buff; 85 | result += u16; 86 | } 87 | u16 = from64to16(result); 88 | if (odd) u16 = ((u16 >> 8) & 0xff) | ((u16 & 0xff) << 8); 89 | return u16; 90 | } 91 | 92 | uint16_t csum_partial(const void *buff, size_t len) 93 | { 94 | return do_csum(buff,len); 95 | } 96 | 97 | uint16_t csum_tcpudp_magic(uint32_t saddr, uint32_t daddr, size_t len, uint8_t proto, uint16_t sum) 98 | { 99 | return ~from64to16((uint64_t)saddr + daddr + sum + htonl(len+proto)); 100 | } 101 | 102 | uint16_t ip4_compute_csum(const void *buff, size_t len) 103 | { 104 | return ~from64to16(do_csum(buff,len)); 105 | } 106 | void ip4_fix_checksum(struct iphdr *ip) 107 | { 108 | ip->check = 0; 109 | ip->check = ip4_compute_csum(ip, ip->ihl<<2); 110 | } 111 | 112 | uint16_t csum_ipv6_magic(const void *saddr, const void *daddr, size_t len, uint8_t proto, uint16_t sum) 113 | { 114 | uint64_t a = (uint64_t)sum + htonl(len+proto) + 115 | *(uint32_t*)saddr + *((uint32_t*)saddr+1) + *((uint32_t*)saddr+2) + *((uint32_t*)saddr+3) + 116 | *(uint32_t*)daddr + *((uint32_t*)daddr+1) + *((uint32_t*)daddr+2) + *((uint32_t*)daddr+3); 117 | return ~from64to16(a); 118 | } 119 | -------------------------------------------------------------------------------- /ipobfs/checksum.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | uint16_t csum_partial(const void *buff, size_t len); 9 | uint16_t csum_tcpudp_magic(uint32_t saddr, uint32_t daddr, size_t len, uint8_t proto, uint16_t sum); 10 | uint16_t csum_ipv6_magic(const void *saddr, const void *daddr, size_t len, uint8_t proto, uint16_t sum); 11 | uint16_t ip4_compute_csum(const void *buff, size_t len); 12 | void ip4_fix_checksum(struct iphdr *ip); 13 | -------------------------------------------------------------------------------- /ipobfs/ipobfs.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include "checksum.h" 21 | 22 | #define NF_DROP 0 23 | #define NF_ACCEPT 1 24 | 25 | 26 | 27 | typedef enum 28 | { 29 | none = 0, fix, valid 30 | } csum_mode; 31 | 32 | struct params_s 33 | { 34 | bool debug; 35 | csum_mode csum; 36 | int qnum; 37 | uint8_t ipp_xor; 38 | uint32_t data_xor; 39 | size_t data_xor_offset, data_xor_len; 40 | }; 41 | 42 | struct params_s params; 43 | 44 | 45 | static bool proto_check_ipv4(uint8_t *data, size_t len) 46 | { 47 | return len >= 20 && (data[0] & 0xF0) == 0x40 && 48 | len >= ((data[0] & 0x0F) << 2); 49 | } 50 | // move to transport protocol 51 | static void proto_skip_ipv4(uint8_t **data, size_t *len) 52 | { 53 | size_t l; 54 | 55 | l = (**data & 0x0F) << 2; 56 | *data += l; 57 | *len -= l; 58 | } 59 | 60 | static bool proto_check_ipv6(uint8_t *data, size_t len) 61 | { 62 | return len >= 40 && (data[0] & 0xF0) == 0x60 && 63 | (len - 40) >= htons(*(uint16_t*)(data + 4)); // payload length 64 | } 65 | static void proto_skip_ipv6_base_header(uint8_t **data, size_t *len) 66 | { 67 | *data += 40; *len -= 40; // skip ipv6 base header 68 | } 69 | 70 | 71 | static bool ip4_fragmented(struct iphdr *ip) 72 | { 73 | // fragment_offset!=0 or more fragments flag 74 | return !!(ntohs(ip->frag_off) & 0x3FFF); 75 | } 76 | static uint16_t ip4_frag_offset(struct iphdr *ip) 77 | { 78 | return (ntohs(ip->frag_off) & 0x1FFF)<<3; 79 | } 80 | 81 | 82 | static void fix_transport_checksum(struct iphdr *ip, struct ip6_hdr *ip6, uint8_t *tdata, size_t tlen) 83 | { 84 | uint8_t proto; 85 | uint16_t check, check_old; 86 | 87 | if (!!ip == !!ip6) return; // must be only one 88 | 89 | if (ip && ip4_fragmented(ip)) 90 | { 91 | if (params.debug) printf("fix_transport_checksum not fixing checksum in fragmented ip\n"); 92 | return; // no way we can compute valid checksum for ip fragment 93 | } 94 | 95 | proto = ip ? ip->protocol : ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt; 96 | switch (proto) 97 | { 98 | case IPPROTO_TCP: 99 | if (tlen < sizeof(struct tcphdr)) return; 100 | check_old = ((struct tcphdr*)tdata)->check; 101 | ((struct tcphdr*)tdata)->check = 0; 102 | break; 103 | case IPPROTO_UDP: 104 | if (tlen < sizeof(struct udphdr)) return; 105 | check_old = ((struct udphdr*)tdata)->check; 106 | ((struct udphdr*)tdata)->check = 0; 107 | break; 108 | default: 109 | return; 110 | } 111 | 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)); 112 | switch (proto) 113 | { 114 | case IPPROTO_TCP: 115 | ((struct tcphdr*)tdata)->check = check; 116 | break; 117 | case IPPROTO_UDP: 118 | ((struct udphdr*)tdata)->check = check; 119 | break; 120 | } 121 | if (params.debug) printf("fix_transport_checksum pver=%c proto=%u %04X => %04X\n", ip ? '4' : '6', proto, check_old, check); 122 | 123 | } 124 | 125 | 126 | static uint32_t rotl32(uint32_t value, unsigned int count) 127 | { 128 | return value << count | value >> (32 - count); 129 | } 130 | static uint32_t rotr32 (uint32_t value, unsigned int count) 131 | { 132 | return value >> count | value << (32 - count); 133 | } 134 | // this function can xor multi-chunked payload. data point to a chunk, len means chunk length, data_pos tells byte offset of this chunk 135 | // on some architectures misaligned access cause exception , kernel transparently fixes it, but it costs huge slowdown - 15-20 times slower 136 | 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) 137 | { 138 | if (!data_xor_len) data_xor_len=0xFFFF; 139 | if (data_xor_offset<(data_pos+len) && (data_xor_offset+data_xor_len)>data_pos) 140 | { 141 | size_t start=data_xor_offset>data_pos ? data_xor_offset-data_pos : 0; 142 | if (start=8 ; len-=8,data+=8) *(uint64_t*)data ^= nxor; 160 | if (len>=4) 161 | { 162 | *(uint32_t*)data ^= (uint32_t)nxor; 163 | len-=4; data+=4; 164 | } 165 | } 166 | while(len--) *data++ ^= (uint8_t)(xor=rotl32(xor,8)); 167 | } 168 | } 169 | } 170 | static void modify_packet_payload(struct iphdr *ip, struct ip6_hdr *ip6, uint8_t *tdata, size_t tlen, int indev, int outdev) 171 | { 172 | if (tlen > params.data_xor_offset) 173 | { 174 | if (params.debug) printf("modify_packet_payload data_xor %08X\n", params.data_xor); 175 | 176 | _modify_packet_payload(tdata,tlen, ip ? ip4_frag_offset(ip) : 0,params.data_xor,params.data_xor_offset,params.data_xor_len); 177 | 178 | // incoming packets : we cant disable sum check in kernel. instead we forcibly make checksum valid 179 | // if indev==0 it means packet was locally generated. no need to fix checksum because its supposed to be valid 180 | if ((params.csum == valid || params.csum == fix && indev)) fix_transport_checksum(ip, ip6, tdata, tlen); 181 | } 182 | } 183 | 184 | 185 | static bool modify_ip4_packet(uint8_t *data, size_t len, int indev, int outdev) 186 | { 187 | bool bRes = false; 188 | uint8_t bOutgoing=!indev; 189 | struct iphdr *iphdr = (struct iphdr*)data; 190 | 191 | // do data modification with original ip protocol. necessary for checksums 192 | for(uint8_t b=0;b<=1;b++) 193 | { 194 | if (params.data_xor && b!=bOutgoing) 195 | { 196 | uint8_t *tdata = data; 197 | size_t tlen = len; 198 | proto_skip_ipv4(&tdata, &tlen); 199 | modify_packet_payload(iphdr, NULL, tdata, tlen, indev, outdev); 200 | bRes = true; 201 | } 202 | if (params.ipp_xor && b==bOutgoing) 203 | { 204 | uint8_t proto = iphdr->protocol; 205 | iphdr->protocol ^= params.ipp_xor; 206 | if (params.debug) printf("modify_ipv4_packet proto %u=>%u\n", proto, iphdr->protocol); 207 | ip4_fix_checksum(iphdr); 208 | bRes = true; 209 | } 210 | } 211 | return bRes; 212 | } 213 | static bool modify_ip6_packet(uint8_t *data, size_t len, int indev, int outdev) 214 | { 215 | bool bRes = false; 216 | uint8_t bOutgoing=!indev; 217 | struct ip6_hdr *ip6hdr = (struct ip6_hdr*)data; 218 | 219 | // do data modification with original ip protocol. necessary for checksums 220 | for(uint8_t b=0;b<=1;b++) 221 | { 222 | if (params.data_xor && b!=bOutgoing) 223 | { 224 | uint8_t *tdata = data; 225 | size_t tlen = len; 226 | proto_skip_ipv6_base_header(&tdata, &tlen); 227 | modify_packet_payload(NULL, ip6hdr, tdata, tlen, indev, outdev); 228 | bRes = true; 229 | } 230 | if (params.ipp_xor && b==bOutgoing) 231 | { 232 | uint8_t proto = ip6hdr->ip6_ctlun.ip6_un1.ip6_un1_nxt; 233 | ip6hdr->ip6_ctlun.ip6_un1.ip6_un1_nxt ^= params.ipp_xor; 234 | if (params.debug) printf("modify_ipv6_packet proto %u=>%u\n", proto, ip6hdr->ip6_ctlun.ip6_un1.ip6_un1_nxt); 235 | bRes = true; 236 | } 237 | } 238 | return bRes; 239 | } 240 | 241 | 242 | typedef enum 243 | { 244 | pass = 0, modify, drop 245 | } packet_process_result; 246 | static packet_process_result processPacketData(uint8_t *data_pkt, size_t len_pkt, int indev, int outdev) 247 | { 248 | struct iphdr *iphdr = NULL; 249 | struct ip6_hdr *ip6hdr = NULL; 250 | bool bMod = false; 251 | 252 | if (proto_check_ipv4(data_pkt, len_pkt)) 253 | bMod = modify_ip4_packet(data_pkt, len_pkt, indev, outdev); 254 | else if (proto_check_ipv6(data_pkt, len_pkt)) 255 | bMod = modify_ip6_packet(data_pkt, len_pkt, indev, outdev); 256 | return bMod ? modify : pass; 257 | } 258 | 259 | 260 | static int cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *cookie) 261 | { 262 | __be32 id; 263 | size_t len; 264 | struct nfqnl_msg_packet_hdr *ph; 265 | uint8_t *data; 266 | 267 | ph = nfq_get_msg_packet_hdr(nfa); 268 | id = ph ? ntohl(ph->packet_id) : 0; 269 | 270 | len = nfq_get_payload(nfa, &data); 271 | if (params.debug) printf("packet: id=%d len=%zu\n", id, len); 272 | if (len >= 0) 273 | { 274 | switch (processPacketData(data, len, nfq_get_indev(nfa), nfq_get_outdev(nfa))) 275 | { 276 | case modify: return nfq_set_verdict(qh, id, NF_ACCEPT, len, data); 277 | case drop: return nfq_set_verdict(qh, id, NF_DROP, 0, NULL); 278 | } 279 | } 280 | 281 | return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); 282 | } 283 | 284 | static bool setpcap(cap_value_t *caps, int ncaps) 285 | { 286 | cap_t capabilities; 287 | 288 | if (!(capabilities = cap_init())) 289 | return false; 290 | 291 | if (ncaps && (cap_set_flag(capabilities, CAP_PERMITTED, ncaps, caps, CAP_SET) || 292 | cap_set_flag(capabilities, CAP_EFFECTIVE, ncaps, caps, CAP_SET))) 293 | { 294 | cap_free(capabilities); 295 | return false; 296 | } 297 | if (cap_set_proc(capabilities)) 298 | { 299 | cap_free(capabilities); 300 | return false; 301 | } 302 | cap_free(capabilities); 303 | return true; 304 | } 305 | static int getmaxcap() 306 | { 307 | int maxcap = CAP_LAST_CAP; 308 | FILE *F = fopen("/proc/sys/kernel/cap_last_cap", "r"); 309 | if (F) 310 | { 311 | int n = fscanf(F, "%d", &maxcap); 312 | fclose(F); 313 | } 314 | return maxcap; 315 | 316 | } 317 | static bool dropcaps() 318 | { 319 | // must have CAP_SETPCAP at the end. its required to clear bounding set 320 | cap_value_t cap_values[] = { CAP_NET_ADMIN,CAP_SETPCAP }; 321 | int capct = sizeof(cap_values) / sizeof(*cap_values); 322 | int maxcap = getmaxcap(); 323 | 324 | if (setpcap(cap_values, capct)) 325 | { 326 | for (int cap = 0; cap <= maxcap; cap++) 327 | { 328 | if (cap_drop_bound(cap)) 329 | { 330 | fprintf(stderr, "could not drop cap %d\n", cap); 331 | perror("cap_drop_bound"); 332 | } 333 | } 334 | } 335 | // now without CAP_SETPCAP 336 | if (!setpcap(cap_values, capct - 1)) 337 | { 338 | perror("setpcap"); 339 | return false; 340 | } 341 | return true; 342 | } 343 | static bool droproot(uid_t uid, gid_t gid) 344 | { 345 | if (uid || gid) 346 | { 347 | if (prctl(PR_SET_KEEPCAPS, 1L)) 348 | { 349 | perror("prctl(PR_SET_KEEPCAPS): "); 350 | return false; 351 | } 352 | if (setgid(gid)) 353 | { 354 | perror("setgid: "); 355 | return false; 356 | } 357 | if (setuid(uid)) 358 | { 359 | perror("setuid: "); 360 | return false; 361 | } 362 | } 363 | return dropcaps(); 364 | } 365 | 366 | static void daemonize() 367 | { 368 | int pid; 369 | 370 | pid = fork(); 371 | if (pid == -1) 372 | { 373 | perror("fork: "); 374 | exit(2); 375 | } 376 | else if (pid != 0) 377 | exit(0); 378 | 379 | if (setsid() == -1) 380 | exit(2); 381 | if (chdir("/") == -1) 382 | exit(2); 383 | close(STDIN_FILENO); 384 | close(STDOUT_FILENO); 385 | close(STDERR_FILENO); 386 | /* redirect fd's 0,1,2 to /dev/null */ 387 | open("/dev/null", O_RDWR); 388 | int fd; 389 | /* stdin */ 390 | fd = dup(0); 391 | /* stdout */ 392 | fd = dup(0); 393 | /* stderror */ 394 | } 395 | 396 | static bool writepid(const char *filename) 397 | { 398 | FILE *F; 399 | if (!(F = fopen(filename, "w"))) 400 | return false; 401 | fprintf(F, "%d", getpid()); 402 | fclose(F); 403 | return true; 404 | } 405 | 406 | 407 | static void exithelp() 408 | { 409 | printf( 410 | " --qnum=\n" 411 | " --daemon\t\t\t; daemonize\n" 412 | " --pidfile=\t\t; write pid to file\n" 413 | " --user=\t\t; drop root privs\n" 414 | " --debug\t\t\t; print debug info\n" 415 | " --uid=uid[:gid]\t\t; drop root privs\n" 416 | " --ipproto-xor=0..255|0x00-0xFF\t; xor protocol ID with given value\n" 417 | " --data-xor=0xDEADBEAF\t\t; xor IP payload (after IP header) with 32-bit HEX value\n" 418 | " --data-xor-offset=\t; start xoring at specified position after IP header end\n" 419 | " --data-xor-len=\t\t; xor block max length. xor entire packet after offset if not specified\n" 420 | " --csum=none|fix|valid\t\t; transport header checksum : none = dont touch, fix = ignore checksum on incoming packets, valid = always make checksum valid\n" 421 | ); 422 | exit(1); 423 | } 424 | 425 | int main(int argc, char **argv) 426 | { 427 | struct nfq_handle *h; 428 | struct nfq_q_handle *qh; 429 | int fd; 430 | int rv; 431 | char buf[16384] __attribute__((aligned)); 432 | int option_index = 0; 433 | int v; 434 | bool daemon = false; 435 | uid_t uid = 0; 436 | gid_t gid = 0; 437 | char pidfile[256]; 438 | 439 | srand(time(NULL)); 440 | 441 | memset(¶ms, 0, sizeof(params)); 442 | params.data_xor_len = 0xFFFF; 443 | *pidfile = 0; 444 | 445 | const struct option long_options[] = { 446 | {"qnum",required_argument,0,0}, // optidx=0 447 | {"daemon",no_argument,0,0}, // optidx=1 448 | {"pidfile",required_argument,0,0}, // optidx=2 449 | {"user",required_argument,0,0 },// optidx=3 450 | {"uid",required_argument,0,0 },// optidx=4 451 | {"debug",no_argument,0,0 },// optidx=5 452 | {"ipproto-xor",required_argument,0,0}, // optidx=6 453 | {"data-xor",required_argument,0,0}, // optidx=7 454 | {"data-xor-offset",required_argument,0,0}, // optidx=8 455 | {"data-xor-len",required_argument,0,0}, // optidx=9 456 | {"csum",required_argument,0,0}, // optidx=10 457 | {NULL,0,NULL,0} 458 | }; 459 | if (argc < 2) exithelp(); 460 | while ((v = getopt_long_only(argc, argv, "", long_options, &option_index)) != -1) 461 | { 462 | if (v) exithelp(); 463 | switch (option_index) 464 | { 465 | case 0: /* qnum */ 466 | params.qnum = atoi(optarg); 467 | if (params.qnum < 0 || params.qnum>65535) 468 | { 469 | fprintf(stderr, "bad qnum\n"); 470 | exit(1); 471 | } 472 | break; 473 | case 1: /* daemon */ 474 | daemon = true; 475 | break; 476 | case 2: /* pidfile */ 477 | strncpy(pidfile, optarg, sizeof(pidfile)); 478 | pidfile[sizeof(pidfile) - 1] = '\0'; 479 | break; 480 | case 3: /* user */ 481 | { 482 | struct passwd *pwd = getpwnam(optarg); 483 | if (!pwd) 484 | { 485 | fprintf(stderr, "non-existent username supplied\n"); 486 | exit(1); 487 | } 488 | uid = pwd->pw_uid; 489 | gid = pwd->pw_gid; 490 | break; 491 | } 492 | case 4: /* uid */ 493 | gid = 0x7FFFFFFF; // default git. drop gid=0 494 | if (!sscanf(optarg, "%u:%u", &uid, &gid)) 495 | { 496 | fprintf(stderr, "--uid should be : uid[:gid]\n"); 497 | exit(1); 498 | } 499 | break; 500 | case 5: /* debug */ 501 | params.debug = true; 502 | break; 503 | case 6: /* ipproto-xor */ 504 | { 505 | uint u; 506 | if (!sscanf(optarg, "0x%X", &u) && !sscanf(optarg, "%u", &u) || u > 255) 507 | { 508 | fprintf(stderr, "ipp-xor should be 1-byte decimal or 0x\n"); 509 | exit(1); 510 | } 511 | params.ipp_xor = (uint8_t)u; 512 | } 513 | break; 514 | case 7: /* data-xor */ 515 | if (!sscanf(optarg, "0x%X", ¶ms.data_xor)) 516 | { 517 | fprintf(stderr, "data-xor should be 32 bit HEX starting with 0x\n"); 518 | exit(1); 519 | } 520 | break; 521 | case 8: /* data-xor-offset */ 522 | params.data_xor_offset = (size_t)atoi(optarg); 523 | break; 524 | case 9: /* data-xor-len */ 525 | params.data_xor_len = (size_t)atoi(optarg); 526 | break; 527 | case 10: /* csum */ 528 | if (!strcmp(optarg, "none")) 529 | params.csum = none; 530 | else if (!strcmp(optarg, "fix")) 531 | params.csum = fix; 532 | else if (!strcmp(optarg, "valid")) 533 | params.csum = valid; 534 | else 535 | { 536 | fprintf(stderr, "invalid csum parameter\n"); 537 | exit(1); 538 | } 539 | break; 540 | } 541 | } 542 | 543 | if (daemon) daemonize(); 544 | 545 | h = NULL; 546 | qh = NULL; 547 | 548 | if (*pidfile && !writepid(pidfile)) 549 | { 550 | fprintf(stderr, "could not write pidfile\n"); 551 | goto exiterr; 552 | } 553 | 554 | printf("opening library handle\n"); 555 | h = nfq_open(); 556 | if (!h) { 557 | fprintf(stderr, "error during nfq_open()\n"); 558 | goto exiterr; 559 | } 560 | 561 | printf("unbinding existing nf_queue handler for AF_INET (if any)\n"); 562 | if (nfq_unbind_pf(h, AF_INET) < 0) { 563 | fprintf(stderr, "error during nfq_unbind_pf()\n"); 564 | goto exiterr; 565 | } 566 | 567 | printf("binding nfnetlink_queue as nf_queue handler for AF_INET\n"); 568 | if (nfq_bind_pf(h, AF_INET) < 0) { 569 | fprintf(stderr, "error during nfq_bind_pf()\n"); 570 | goto exiterr; 571 | } 572 | 573 | printf("binding this socket to queue '%u'\n", params.qnum); 574 | qh = nfq_create_queue(h, params.qnum, &cb, NULL); 575 | if (!qh) { 576 | fprintf(stderr, "error during nfq_create_queue()\n"); 577 | goto exiterr; 578 | } 579 | 580 | printf("setting copy_packet mode\n"); 581 | if (nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff) < 0) { 582 | fprintf(stderr, "can't set packet_copy mode\n"); 583 | goto exiterr; 584 | } 585 | 586 | if (!droproot(uid, gid)) goto exiterr; 587 | fprintf(stderr, "Running as UID=%u GID=%u\n", getuid(), getgid()); 588 | 589 | fd = nfq_fd(h); 590 | while ((rv = recv(fd, buf, sizeof(buf), 0)) && rv >= 0) 591 | { 592 | int r = nfq_handle_packet(h, buf, rv); 593 | if (r) fprintf(stderr, "nfq_handle_packet error %d\n", r); 594 | } 595 | 596 | printf("unbinding from queue 0\n"); 597 | nfq_destroy_queue(qh); 598 | 599 | #ifdef INSANE 600 | /* normally, applications SHOULD NOT issue this command, since 601 | * it detaches other programs/sockets from AF_INET, too ! */ 602 | printf("unbinding from AF_INET\n"); 603 | nfq_unbind_pf(h, AF_INET); 604 | #endif 605 | 606 | printf("closing library handle\n"); 607 | nfq_close(h); 608 | 609 | return 0; 610 | 611 | exiterr: 612 | if (qh) nfq_destroy_queue(qh); 613 | if (h) nfq_close(h); 614 | return 1; 615 | } 616 | -------------------------------------------------------------------------------- /ipobfs_mod/Makefile: -------------------------------------------------------------------------------- 1 | obj-m += ipobfs.o 2 | KERNELDIR ?= /lib/modules/$(shell uname -r)/build 3 | KERNELRELEASE ?= $(shell uname -r) 4 | PWD := $(shell pwd) 5 | DEPMOD ?= depmod 6 | STRIP ?= strip 7 | 8 | all: 9 | @$(MAKE) -C $(KERNELDIR) M=$(PWD) modules 10 | 11 | clean: 12 | @$(MAKE) -C $(KERNELDIR) M=$(PWD) clean 13 | 14 | mod-install: 15 | $(STRIP) --strip-debug *.ko 16 | @$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install 17 | $(DEPMOD) -a $(KERNELRELEASE) 18 | 19 | install : mod-install 20 | -------------------------------------------------------------------------------- /ipobfs_mod/ipobfs.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | MODULE_DESCRIPTION("ip obfuscator. xor ip protocol or data payload with some values. supports multiple profiles triggered by fwmark bits"); 12 | MODULE_AUTHOR("bol-van"); 13 | MODULE_LICENSE("GPL"); 14 | 15 | #define MAX_MARK 32 16 | 17 | typedef enum 18 | { 19 | none=0,fix,valid 20 | } t_csum; 21 | static t_csum csum[MAX_MARK]; 22 | static int ct_csum; 23 | 24 | static bool debug=false; 25 | static uint mark[MAX_MARK], markmask=0; 26 | static int ct_mark=0; 27 | static uint data_xor[MAX_MARK]; 28 | static int ct_data_xor=0; 29 | static uint data_xor_offset[MAX_MARK]; 30 | static int ct_data_xor_offset=0; 31 | static uint data_xor_len[MAX_MARK]; 32 | static int ct_data_xor_len=0; 33 | static ushort ipp_xor[MAX_MARK]; 34 | static int ct_ipp_xor=0; 35 | 36 | static char *prehook_s[MAX_MARK]; 37 | static unsigned int prehook[MAX_MARK]; 38 | static int ct_prehook; 39 | static char *pre_s[MAX_MARK]; 40 | static int pre[MAX_MARK]; 41 | static int ct_pre; 42 | static char *posthook_s[MAX_MARK]; 43 | static unsigned int posthook[MAX_MARK]; 44 | static int ct_posthook; 45 | static char *post_s[MAX_MARK]; 46 | static int post[MAX_MARK]; 47 | static int ct_post; 48 | static char *csum_s[MAX_MARK]; 49 | 50 | module_param(debug,bool,0640); 51 | 52 | module_param_array(mark,uint,&ct_mark,0640); 53 | module_param(markmask,uint,0640); 54 | 55 | module_param_array(data_xor,uint,&ct_data_xor,0640); 56 | module_param_array(data_xor_offset,uint,&ct_data_xor_offset,0640); 57 | module_param_array(data_xor_len,uint,&ct_data_xor_len,0640); 58 | module_param_array(ipp_xor,ushort,&ct_ipp_xor,0640); 59 | 60 | module_param_array_named(prehook,prehook_s,charp,&ct_prehook,0440); 61 | module_param_array_named(pre,pre_s,charp,&ct_pre,0440); 62 | module_param_array_named(posthook,posthook_s,charp,&ct_posthook,0440); 63 | module_param_array_named(post,post_s,charp,&ct_post,0440); 64 | 65 | module_param_array_named(csum,csum_s,charp,&ct_csum,0440); 66 | 67 | MODULE_PARM_DESC(debug, "printk debug info"); 68 | MODULE_PARM_DESC(mark, "fwmark filters : 0x100,0x200,0x400. if markmask not specified, markmask=mark for each profile"); 69 | MODULE_PARM_DESC(markmask, "fwmark filter mask : common mask for all profiles. if not specified, markmask=mark for each profile"); 70 | MODULE_PARM_DESC(data_xor, "uint32 data xor : 0xDEADBEAF,0x01020304,0"); 71 | MODULE_PARM_DESC(data_xor_offset, "start xoring from position : 4,4,8"); 72 | MODULE_PARM_DESC(data_xor_len, "xor no more than : 0,0,16"); 73 | MODULE_PARM_DESC(ipp_xor, "xor ip protocol with : 0,0x80,42"); 74 | MODULE_PARM_DESC(prehook, "input hook : none, prerouting (default), input, forward"); 75 | MODULE_PARM_DESC(pre, "input hook priority : mangle (default), raw, filter or "); 76 | MODULE_PARM_DESC(posthook, "output hook : none, postrouting (default), output, forward"); 77 | MODULE_PARM_DESC(post, "output hook priority : mangle (default), raw, filter or "); 78 | MODULE_PARM_DESC(csum, "csum mode : none = invalid csums are ok, fix = valid csums on original outgoing packets, valid = valid csums on obfuscated packets"); 79 | 80 | 81 | #define GET_PARAM(name,idx) (idxcheck = 0; 208 | ip->check = ip_fast_csum(ip,ip->ihl); 209 | } 210 | 211 | 212 | 213 | static bool ip4_fragmented(struct iphdr *ip) 214 | { 215 | // fragment_offset!=0 or more fragments flag 216 | return !!(ntohs(ip->frag_off) & 0x3FFF); 217 | } 218 | static uint16_t ip4_frag_offset(struct iphdr *ip) 219 | { 220 | return (ntohs(ip->frag_off) & 0x1FFF)<<3; 221 | } 222 | 223 | static u8 ip_proto_ver(const void *net_header) 224 | { 225 | return (*(u8*)net_header)>>4; 226 | } 227 | static u8 transport_proto(const void *net_header) 228 | { 229 | switch(ip_proto_ver(net_header)) 230 | { 231 | case 4: 232 | return ((struct iphdr*)net_header)->protocol; 233 | case 6: 234 | return ((struct ipv6hdr*)net_header)->nexthdr; 235 | default: 236 | return 0; 237 | } 238 | } 239 | 240 | static void fix_transport_checksum(struct sk_buff *skb) 241 | { 242 | uint tlen; 243 | u8 *pn, *pt, pver, proto; 244 | __sum16 check=0, check_old; 245 | 246 | if (!skb_transport_header_was_set(skb)) return; 247 | 248 | pn = skb_network_header(skb); 249 | pver = ip_proto_ver(pn); 250 | if (pver==4 && ip4_fragmented((struct iphdr*)pn)) 251 | { 252 | if (debug) printk(KERN_DEBUG "ipobfs: fix_transport_checksum not fixing checksum in fragmented ip\n"); 253 | return; // no way we can compute valid checksum for ip fragment 254 | } 255 | proto = transport_proto(pn); 256 | pt = skb_transport_header(skb); 257 | tlen = skb_headlen(skb) - (skb->transport_header - skb->network_header); 258 | switch(proto) 259 | { 260 | case IPPROTO_TCP : 261 | if (tlencheck; 263 | ((struct tcphdr*)pt)->check = 0; 264 | break; 265 | case IPPROTO_UDP: 266 | if (tlencheck; 268 | ((struct udphdr*)pt)->check = 0; 269 | break; 270 | default: 271 | return; 272 | } 273 | switch(pver) 274 | { 275 | case 4: 276 | check = csum_tcpudp_magic(((struct iphdr*)pn)->saddr, ((struct iphdr*)pn)->daddr, tlen, proto, csum_partial(pt, tlen, 0)); 277 | break; 278 | case 6: 279 | check = csum_ipv6_magic(&((struct ipv6hdr*)pn)->saddr, &((struct ipv6hdr*)pn)->daddr, tlen, proto, csum_partial(pt, tlen, 0)); 280 | break; 281 | } 282 | switch(proto) 283 | { 284 | case IPPROTO_TCP: 285 | ((struct tcphdr*)pt)->check = check; 286 | break; 287 | case IPPROTO_UDP: 288 | ((struct udphdr*)pt)->check = check; 289 | break; 290 | } 291 | if (debug) printk(KERN_DEBUG "ipobfs: fix_transport_checksum pver=%u proto=%u tlen=%u %04X => %04X\n",pver,proto,tlen,check_old,check); 292 | } 293 | 294 | 295 | 296 | static u32 rotr32 (u32 value, uint count) 297 | { 298 | return value >> count | value << (32 - count); 299 | } 300 | static u32 rotl32 (u32 value, uint count) 301 | { 302 | return value << count | value >> (32 - count); 303 | } 304 | // this function can xor multi-chunked payload. data point to a chunk, len means chunk length, data_pos tells byte offset of this chunk 305 | // on some architectures misaligned access cause exception , kernel transparently fixes it, but it costs huge slowdown - 15-20 times slower 306 | static void modify_packet_payload(u8 *data,uint len,uint data_pos, u32 data_xor, uint data_xor_offset, uint data_xor_len) 307 | { 308 | if (data_xor_offset<(data_pos+len) && (data_xor_offset+data_xor_len)>data_pos) 309 | { 310 | uint start=data_xor_offset>data_pos ? data_xor_offset-data_pos : 0; 311 | if (start=8 ; len-=8,data+=8) *(u64*)data ^= nxor; 329 | if (len>=4) 330 | { 331 | *(u32*)data ^= (u32)nxor; 332 | len-=4; data+=4; 333 | } 334 | } 335 | while(len--) *data++ ^= (u8)(xor=rotl32(xor,8)); 336 | } 337 | } 338 | } 339 | static void modify_skb_payload(struct sk_buff *skb,int idx,bool bOutgoing) 340 | { 341 | uint len; 342 | u8 *p,*pn,pver; 343 | t_csum csum_mode; 344 | 345 | if (!skb_transport_header_was_set(skb)) return; 346 | 347 | len = skb_headlen(skb); 348 | p = skb_transport_header(skb); 349 | len -= skb->transport_header - skb->network_header; 350 | csum_mode=GET_PARAM(csum,idx); 351 | 352 | // dont linearize if possible 353 | if (skb_is_nonlinear(skb)) 354 | { 355 | uint last_mod_offset=GET_PARAM(data_xor_offset,idx)+GET_DATA_XOR_LEN(idx); 356 | if(csum_mode==fix || csum_mode==valid || last_mod_offset>len) 357 | { 358 | 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)); 359 | if (skb_linearize(skb)) 360 | { 361 | if (debug) printk(KERN_DEBUG "ipobfs: failed to linearize skb"); 362 | return; 363 | } 364 | len = skb_headlen(skb); 365 | p = skb_transport_header(skb); 366 | len -= skb->transport_header - skb->network_header; 367 | } 368 | else 369 | 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)); 370 | } 371 | 372 | if (bOutgoing && csum_mode==fix) fix_transport_checksum(skb); 373 | 374 | pn = skb_network_header(skb); 375 | pver = ip_proto_ver(pn); 376 | 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)); 377 | 378 | 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)); 379 | if (csum_mode==valid) fix_transport_checksum(skb); 380 | } 381 | 382 | static void modify_skb_ipp(struct sk_buff *skb,int idx) 383 | { 384 | uint8_t pver,proto_old=0,proto_new=0; 385 | switch(pver = ip_proto_ver(skb_network_header(skb))) 386 | { 387 | case 4: 388 | { 389 | struct iphdr *ip = ip_hdr(skb); 390 | proto_old = ip->protocol; 391 | proto_new = ip->protocol ^= (u8)GET_PARAM(ipp_xor,idx); 392 | ip4_fix_checksum(ip); 393 | break; 394 | } 395 | case 6: 396 | { 397 | struct ipv6hdr *ip6 = ipv6_hdr(skb); 398 | proto_old = ip6->nexthdr; 399 | proto_new = ip6->nexthdr ^= (u8)GET_PARAM(ipp_xor,idx); 400 | break; 401 | } 402 | } 403 | if (debug) printk(KERN_DEBUG "ipobfs: modify_skb_ipp pver=%u proto %u=>%u\n",pver,proto_old,proto_new); 404 | } 405 | 406 | static uint hook_ip(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) 407 | { 408 | int idx = find_mark(skb->mark); 409 | if (idx!=-1) 410 | { 411 | bool bOutgoing = ((t_hook_id*)priv)->bOutgoing; 412 | if (debug) 413 | printk(KERN_DEBUG "ipobfs: hook_ip %s mark_idx=%d hook=%s pri=%s in=%s out=%s\n", 414 | bOutgoing ? "out" : "in", 415 | idx, 416 | nf_string_from_hooknum(state->hook), 417 | nf_string_from_priority(((t_hook_id*)priv)->priority), 418 | state->in ? state->in->name : "null", state->out ? state->out->name : "null"); 419 | if ((!bOutgoing && ((t_hook_id*)priv)->priority==pre[idx] && state->hook==prehook[idx]) || 420 | (bOutgoing && ((t_hook_id*)priv)->priority==post[idx] && state->hook==posthook[idx])) 421 | { 422 | skb->ip_summed = CHECKSUM_UNNECESSARY; 423 | // do data modification with original ip protocol. necessary for checksums 424 | if (bOutgoing) 425 | { 426 | if (GET_PARAM(data_xor,idx)) modify_skb_payload(skb,idx,bOutgoing); 427 | if (GET_PARAM(ipp_xor,idx)) modify_skb_ipp(skb,idx); 428 | } 429 | else 430 | { 431 | if (GET_PARAM(ipp_xor,idx)) modify_skb_ipp(skb,idx); 432 | if (GET_PARAM(data_xor,idx)) modify_skb_payload(skb,idx,bOutgoing); 433 | } 434 | // clear mask bits to avoid processing in post hook 435 | skb->mark &= ~(markmask ? markmask : GET_PARAM(mark,idx)); 436 | } 437 | } 438 | return NF_ACCEPT; 439 | } 440 | 441 | 442 | 443 | 444 | 445 | static struct nf_hook_ops nfhk_pre[MAX_MARK*2], nfhk_post[MAX_MARK*2]; 446 | static int ct_nfhk_pre,ct_nfhk_post; 447 | static t_hook_id hookid_pre[MAX_MARK],hookid_post[MAX_MARK]; 448 | 449 | static int find_hook(const struct nf_hook_ops *nfhk, int ct, unsigned int hooknum, int priority, u8 pf) 450 | { 451 | int i; 452 | for(i=0;i>22&0x3C@0&0xFFFF=16" -j NFQUEUE --queue-num 300 --queue-bypass 3 | iptables -t mangle -I POSTROUTING -o eth0 -p udp --sport 16 -j NFQUEUE --queue-num 300 --queue-bypass 4 | 5 | client ipv4 udp:16 : 6 | 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 7 | iptables -t mangle -I POSTROUTING -o eth0 -p udp --dport 16 -j NFQUEUE --queue-num 300 --queue-bypass 8 | 9 | ./ipobfs --qnum=300 --ipproto-xor=128 --data-xor=0x458A2ECD --data-xor-offset=4 --data-xor-len=44 10 | 11 | 12 | server ipv6 tcp:12345 : 13 | ip6tables -t mangle -I PREROUTING -i eth0 -p 59 -m u32 --u32 "40&0xFFFF=12345" -j NFQUEUE --queue-num 300 --queue-bypass 14 | ip6tables -t mangle -I POSTROUTING -o eth0 -p tcp --sport 12345 -j NFQUEUE --queue-num 300 --queue-bypass 15 | 16 | client ipv6 tcp:12345 : 17 | ip6tables -t mangle -I PREROUTING -i eth0 -p 59 -m u32 --u32 "38&0xFFFF=12345" -j NFQUEUE --queue-num 300 --queue-bypass 18 | ip6tables -t mangle -I POSTROUTING -o eth0 -p tcp --dport 12345 -j NFQUEUE --queue-num 300 --queue-bypass 19 | 20 | ./ipobfs --qnum=300 --ipproto-xor=61 --data-xor=0x458A2ECD --data-xor-offset=4 21 | 22 | 23 | ------------------------------------------------------------------------------------------------------------------------------------ 24 | 25 | 26 | server ipv4 udp:16 : 27 | iptables -t mangle -I PREROUTING -i eth0 -p 145 -m u32 --u32 "0>>22&0x3C@0&0xFFFF=16" -j MARK --set-xmark 0x100/0x100 28 | iptables -t mangle -I POSTROUTING -o eth0 -p udp --sport 16 -j MARK --set-xmark 0x100/0x100 29 | 30 | client ipv4 udp:16 : 31 | 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 32 | iptables -t mangle -I POSTROUTING -o eth0 -p udp --dport 16 -j MARK --set-xmark 0x100/0x100 33 | 34 | rmmod ipobfs 35 | insmod /lib/modules/`uname -r`/extra/ipobfs.ko mark=0x100 ipp_xor=128 data_xor=0x458A2ECD data_xor_offset=4 data_xor_len=44 36 | 37 | 38 | server ipv6 tcp:12345 : 39 | ip6tables -t mangle -I PREROUTING -i eth0 -p 59 -m u32 --u32 "40&0xFFFF=12345" -j MARK --set-xmark 0x200/0x200 40 | ip6tables -t mangle -I POSTROUTING -o eth0 -p tcp --sport 12345 -j MARK --set-xmark 0x200/0x200 41 | 42 | client ipv6 tcp:12345 : 43 | ip6tables -t mangle -I PREROUTING -i eth0 -p 59 -m u32 --u32 "38&0xFFFF=12345" -j MARK --set-xmark 0x200/0x200 44 | ip6tables -t mangle -I POSTROUTING -o eth0 -p tcp --dport 12345 -j MARK --set-xmark 0x200/0x200 45 | 46 | rmmod ipobfs 47 | insmod /lib/modules/`uname -r`/extra/ipobfs.ko mark=0x200 ipp_xor=61 data_xor=0x458A2ECD data_xor_offset=4 48 | -------------------------------------------------------------------------------- /mod_hooks.txt: -------------------------------------------------------------------------------- 1 | ipobfs_mod sets up 2 hooks : one for inbound, one for outbound 2 | 3 | module takes parameters 4 | pre= (default : mangle) 5 | prehook= (default : prerouting) 6 | post= (default : mangle) 7 | posthook= (default : postrouting) 8 | 9 | priority : mangle (default), raw, filter or 10 | raw = NF_IP_PRI_RAW+1 11 | mangle = NF_IP_PRI_MANGLE+1 12 | filter = NF_IP_PRI_FILTER+1 13 | = positive or negative decimal or hex value. hex start with 0x prefix 14 | 15 | hooknum : prerouting (inbound default), input, output, forward, postrouting (outbound default) or 16 | prerouting = NF_INET_PRE_ROUTING 17 | input = NF_INET_LOCAL_IN 18 | forward = NF_INET_FORWARD 19 | output = NF_INET_LOCAL_OUT 20 | postrouting = NF_INET_POST_ROUTING 21 | = decimal or hex value. hex start with 0x prefix 22 | 23 | valid hooknum numbers : 24 | 25 | enum nf_inet_hooks { 26 | NF_INET_PRE_ROUTING = 0, 27 | NF_INET_LOCAL_IN = 1, 28 | NF_INET_FORWARD = 2, 29 | NF_INET_LOCAL_OUT = 3, 30 | NF_INET_POST_ROUTING = 4 31 | }; 32 | 33 | priorities : 34 | 35 | enum nf_ip_hook_priorities { 36 | NF_IP_PRI_FIRST = INT_MIN, 37 | NF_IP_PRI_CONNTRACK_DEFRAG = -400, 38 | NF_IP_PRI_RAW = -300, 39 | NF_IP_PRI_SELINUX_FIRST = -225, 40 | NF_IP_PRI_CONNTRACK = -200, 41 | NF_IP_PRI_MANGLE = -150, 42 | NF_IP_PRI_NAT_DST = -100, 43 | NF_IP_PRI_FILTER = 0, 44 | NF_IP_PRI_SECURITY = 50, 45 | NF_IP_PRI_NAT_SRC = 100, 46 | NF_IP_PRI_SELINUX_LAST = 225, 47 | NF_IP_PRI_CONNTRACK_HELPER = 300, 48 | NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX, 49 | NF_IP_PRI_LAST = INT_MAX, 50 | }; 51 | -------------------------------------------------------------------------------- /openwrt/package/ipobfs/Makefile: -------------------------------------------------------------------------------- 1 | include $(TOPDIR)/rules.mk 2 | include $(INCLUDE_DIR)/kernel.mk 3 | 4 | PKG_NAME:=ipobfs 5 | PKG_RELEASE:=1 6 | PKG_VERSION:=1.0 7 | 8 | PKG_BUILD_DIR:=$(KERNEL_BUILD_DIR)/ipobfs-$(PKG_VERSION) 9 | export KERNELDIR:=$(LINUX_DIR) 10 | 11 | include $(INCLUDE_DIR)/package.mk 12 | 13 | 14 | define Package/ipobfs 15 | SECTION:=net 16 | CATEGORY:=Network 17 | TITLE:=ipobs 18 | SUBMENU:=Zapret 19 | DEPENDS:=+libnetfilter-queue +libcap 20 | endef 21 | 22 | define Build/Prepare 23 | mkdir -p $(PKG_BUILD_DIR) 24 | $(CP) ./ipobfs ./ipobfs_mod $(PKG_BUILD_DIR)/ 25 | endef 26 | 27 | define Build/Compile 28 | $(MAKE) -C $(PKG_BUILD_DIR)/ipobfs $(TARGET_CONFIGURE_OPTS) 29 | $(MAKE) $(KERNEL_MAKEOPTS) -C $(PKG_BUILD_DIR)/ipobfs_mod 30 | endef 31 | 32 | define Package/ipobfs/install 33 | $(INSTALL_DIR) $(1)/opt/ipobfs/ipobfs 34 | $(INSTALL_BIN) $(PKG_BUILD_DIR)/ipobfs/ipobfs $(1)/opt/ipobfs/ipobfs 35 | endef 36 | 37 | include $(INCLUDE_DIR)/kernel-defaults.mk 38 | include $(INCLUDE_DIR)/package-defaults.mk 39 | 40 | 41 | define KernelPackage/ipobfs 42 | SECTION:=kernel 43 | CATEGORY:=Kernel modules 44 | SUBMENU:=Zapret 45 | TITLE:=ipobfs kernel module 46 | FILES:= $(PKG_BUILD_DIR)/ipobfs_mod/ipobfs.$(LINUX_KMOD_SUFFIX) 47 | endef 48 | 49 | 50 | $(eval $(call BuildPackage,ipobfs)) 51 | $(eval $(call KernelPackage,ipobfs)) 52 | -------------------------------------------------------------------------------- /readme.eng.txt: -------------------------------------------------------------------------------- 1 | This project is intended to fight DPI protocol analysis and bypass protocol blocking. 2 | 3 | One of the possible ways to overcome DPI signature analysis is to modify the protocol. 4 | The fastest but not the easiest way is to modify the software itself. 5 | For TCP, obfsproxy exists. However, in the case of VPN - only not very fast solutions (openvpn) work over TCP. 6 | 7 | What to do in case of udp? 8 | If both endpoints are on a external IP, then its possible to modify packets on IP level. 9 | For example, if you have a VPS, and you have an openwrt router at home and external IP from ISP, 10 | then you can use this technique. If one endpoint is behind NAT, then abilities are limited, 11 | but its still possible to tamper with udp/tcp headers and data payload. 12 | 13 | The scheme is as follows: 14 | peer 1 <=> IP obfuscator/deobfuscator <=> network <=> IP obfuscator/deobfuscator <=> peer 2 15 | 16 | In order for a packet to be delivered from peer 1 to peer 2, both having external IPs, 17 | it is enough to have correct IP headers. You can set any protocol number, obfuscate or encrypt IP payload, 18 | including tcp / udp headers. DPI will not understand what it is dealing with. 19 | It will see non-standard IP protocols with unknown content. 20 | 21 | ipobfs 22 | ------ 23 | 24 | NFQUEUE queue handler, IP obfuscator/deobfuscator. 25 | 26 | --qnum= 27 | --daemon ; daemonize 28 | --pidfile= ; write pid to file 29 | --user= ; drop root privs 30 | --debug ; print debug info 31 | --uid=uid[:gid] ; drop root privs 32 | --ipproto-xor=0..255|0x00-0xFF ; xor protocol ID with given value 33 | --data-xor=0xDEADBEAF ; xor IP payload (after IP header) with 32-bit HEX value 34 | --data-xor-offset= ; start xoring at specified position after IP header end 35 | --data-xor-len= ; xor block max length. xor entire packet after offset if not specified 36 | --csum=none|fix|valid ; transport header checksum : none = dont touch, fix = ignore checksum on incoming packets, valid = always make checksum valid 37 | 38 | The XOR operation is symmetric, therefore the same parameters are set for the obfuscator and deobfuscator. 39 | On each side, one instance of the program is launched. 40 | 41 | Filtering outgoing packets is easy because they go open, however, some u32 is required for incoming. 42 | The protocol number ("-p") in the filter is the result of the xor of the original protocol with ipproto-xor. 43 | 44 | server ipv4 udp:16 : 45 | 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 46 | iptables -t mangle -I POSTROUTING -o eth0 -p udp --sport 16 -j NFQUEUE --queue-num 300 --queue-bypass 47 | 48 | client ipv4 udp:16 : 49 | 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 50 | iptables -t mangle -I POSTROUTING -o eth0 -p udp --dport 16 -j NFQUEUE --queue-num 300 --queue-bypass 51 | 52 | ipobfs --qnum=300 --ipproto-xor=128 --data-xor=0x458A2ECD --data-xor-offset=4 --data-xor-len=44 53 | 54 | Why data-xor-offset = 4: tcp and udp protocol headers start with source and destination port numbers, 2 bytes each. 55 | 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 56 | numbers original ports will be transformed and write those values to u32. 57 | Why data-xor-len = 44: an example is given for wireguard. 44 bytes is enough to XOR the udp header and all wireguard headers. 58 | Next come the encrypted wireguard data, it makes no sense to XOR it. 59 | 60 | 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. 61 | On the one hand, such packets can go through middle-boxes, and conntrack can go crazy. 62 | On the other hand, it may even be good. 63 | 64 | There are nuances with ipv6. In ipv6 there is no concept of a protocol number. But there is the concept of "next header". 65 | As in ipv4, you can write anything there. But in practice, this can cause ICMPv6 Type 4 - Parameter Problem messages. 66 | To avoid this, you can cast the protocol to the value 59. It means "no Next Header". 67 | To get "ipproto-xor" parameter, XOR original protocol number with 59. 68 | 69 | udp : ipproto-xor=17^59=42 70 | tcp : ipproto-xor=6^59=61 71 | 72 | server ipv6 tcp:12345 : 73 | ip6tables -t mangle -I PREROUTING -i eth0 -p 59 -m u32 --u32 "40&0xFFFF=12345" -j NFQUEUE --queue-num 300 --queue-bypass 74 | ip6tables -t mangle -I POSTROUTING -o eth0 -p tcp --sport 12345 -j NFQUEUE --queue-num 300 --queue-bypass 75 | 76 | client ipv6 tcp:12345 : 77 | ip6tables -t mangle -I PREROUTING -i eth0 -p 59 -m u32 --u32 "38&0xFFFF=12345" -j NFQUEUE --queue-num 300 --queue-bypass 78 | ip6tables -t mangle -I POSTROUTING -o eth0 -p tcp --dport 12345 -j NFQUEUE --queue-num 300 --queue-bypass 79 | 80 | ipobfs --qnum=300 --ipproto-xor=61 --data-xor=0x458A2ECD --data-xor-offset=4 81 | 82 | IP FRAGMENTATION 83 | If the sending host sends too long packet, it is fragmented at the IP level. 84 | The receiving host only reassembles packets addressed to the host itself. 85 | In the PREROUTING chain packets are still fragmented. 86 | When applying deobfuscation only to a part of the packet, the cheksum inevitably becomes invalid. 87 | csum = fix does not help. 88 | For ipv4, adding a rule to the INPUT chain instead of PREROUTING helps. 89 | Of course, only packets addressed to the host itself are caught, but they come 90 | in NFQEUEUE in already assembled state and correctly deobfuscated. 91 | IP fragmentation is an undesirable, it should be combated by setting the correct MTU 92 | inside the tunnel. There are some protocols that rely on ip fragmentation. These include IKE (without rfc7383). 93 | 94 | IPV6 FRAGMENTATION 95 | Fragmentation is also possible in ipv6, however, it is performed only by the sending host, usually only for 96 | udp and icmp when the frame does not fit into mtu. The header "44" is added to all fragments immediately after the ipv6 header. 97 | Unfortunately, all attempts to catch the reconstructed full frame in various tables failed. 98 | 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. 99 | 100 | CHECKSUMS : 101 | Work with checksums begins when a tcp or udp packet passes through the obfuscator. 102 | For incoming packets, the ipproto-xor operation performed first, and after that it is analyzed whether it is tcp or udp. 103 | For outgoing, the opposite is true. 104 | --csum=none - do not touch checksums at all. if after deobfuscation checksum is invalid, the system will discard the packet. 105 | --csum=fix - checksum ignore mode. its not possible to disable checksum verification inside NFQUEUE. 106 | Instead, on incoming packets checksum is recomputed and replaced, so the system will accept the packet. 107 | --csum=valid - bring the checksum to a valid state for all packets - incoming and outgoing. 108 | This mode is useful when working through NAT which blocks invalid packets. 109 | 110 | Recomputing checksum increases cpu usage. 111 | See also section "NAT break". 112 | 113 | 114 | DISADVANTAGE : 115 | Each packet will be thrown into nfqueue, therefore the speed will decrease significantly. 2-3 times. 116 | If you compare wireguard + ipobfs with openvpn on a soho router, then openvpn will still be slower. 117 | 118 | 119 | ipobfs_mod 120 | ----------- 121 | 122 | The same as ipobfs, but implemented as a linux kernel module. It gives a performance drop of only 20%. 123 | It duplicates ipobfs logic and is compatible with it. 124 | 125 | Its possible to use ipobfs on peer1 and ipobfs_mod on peer2, they will work together. 126 | However, by default ipobfs_mod will produce tcp and udp packets with invalid cheksums, the system 127 | with ipobfs will discarded them. Use csum=fix on ipobfs_mod side. 128 | 129 | The iptables commands are the same, but instead of "-j NFQEUEUE" use "-j MARK --set-xmark". 130 | ipobfs_mod performs packet processing based on fwmark. 131 | 132 | Settings are passed through the kernel module parameters specified in the insmod command. 133 | 134 | server ipv4 udp:16 : 135 | iptables -t mangle -I PREROUTING -i eth0 -p 145 -m u32 --u32 "0>>22&0x3C@0&0xFFFF=16" -j MARK --set-xmark 0x100/0x100 136 | iptables -t mangle -I POSTROUTING -o eth0 -p udp --sport 16 -j MARK --set-xmark 0x100/0x100 137 | 138 | client ipv4 udp:16 : 139 | 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 140 | iptables -t mangle -I POSTROUTING -o eth0 -p udp --dport 16 -j MARK --set-xmark 0x100/0x100 141 | 142 | rmmod ipobfs 143 | insmod /lib/modules/`uname -r`/extra/ipobfs.ko mark=0x100 ipp_xor=128 data_xor=0x458A2ECD data_xor_offset=4 data_xor_len=44 144 | 145 | The module supports up to 32 profiles. Parameter settings for each profile are separated by commas. 146 | For example, the following command combines the functions of 2 NFQUEUE handlers from the previous examples: 147 | 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 148 | It is possible to use different profiles for outgoing and incoming packets. 149 | This will confuse DPI even more by reducing the correlation of in/out streams. 150 | If parameter 'markmask' is set, profile with mask/markmask wins, otherwise mask/mask is searched. 151 | markmask parameter is single for all profiles, no need for commas. 152 | Use markmask if profiles are numerous to not waste single bit for each one. 153 | For example : 0x10/0xf0, 0x20/0xf0, ..., 0xf0/0xf0 154 | 155 | By default, the module sets a hook on incoming packets with priority mangle+1, so that the table mangle was already processed 156 | by the time of the call. If non-standard IP protocols arrive at the input, everything is OK. But if there are packets with 157 | the transport protocol that support checksumming, such as tcp or udp, then modified packets with invalid checksum 158 | will not reach the mangle+1 hook. The module will not receive them. 159 | To solve this problem, specify the pre=raw parameter and do : iptables -t raw -I PREROUTING ... 160 | Outgoing packets can be processed in the usual manner through mangle. 161 | 162 | If you need to work with fragmented ipv4 protocols, replace iptables PREROUTING with INPUT (see the remark in the ipobfs section), 163 | specify the module parameter "prehook=input". 164 | 165 | Parameters pre,prehook,post,posthook are set individually for each profile and must be comma separated. 166 | 167 | The module disables OS-level checksum checking and computing for all processed packets, in some cases 168 | recomputing tcp and udp checksums independently. 169 | If the parameter csum=none, module does not compute checksum at all, allowing sending packets with invalid checksum 170 | before obfuscation. Deobfuscated packets can contain invalid checksum. 171 | If csum=fix, the module takes over the recalculation of the checksum on outgoing packets before the payload is modified, 172 | thereby repeating the functions of the OS or hardware offload. Otherwise OS or hw offload would spoil 2 bytes of data 173 | and after deobfuscation packet would contain incorrect checksum. 174 | If csum=valid, the recalculation of the checksum is done after modifying the payload for both outgoing and incoming packets. 175 | This ensures the visibility of the transmission of packets with a valid checksum. 176 | Checksum correction on the incoming packet is necessary if the device with ipobfs is not the receiver, 177 | but performs the function of a router (forward). So that there is a valid packet on the output interface. 178 | The regular recipient will not accept packets with incorrect checksum. 179 | 180 | The debug = 1 parameter enables debugging output. You will see what is done with each processed packet in dmesg. 181 | It should be used only for debugging. With a large number of packets, the system will slow down significantly 182 | due to excessive output in dmesg. 183 | 184 | You can view and change some ipobfs parameters without reloading the module : /sys/module/ipobfs/parameters 185 | 186 | COMPILING MODULE on traditional linux system : 187 | At first install kernel headers. for debian : 188 | sudo apt-get install linux-headers..... 189 | cd ipobfs_mod 190 | make 191 | sudo make install 192 | 193 | SPEED NOTICE 194 | If only ipproto-xor is specified, slowdown is very close to zero. 195 | With data-xor its preferred not to xor offsets after 100-140 bytes. 196 | This way you can avoid linearizing skb's and save lots of cpu time. 197 | debug=1 option can show whether linearizing happens. 198 | 199 | openwrt 200 | ------- 201 | 202 | On a x64 linux system, download and unzip the SDK from your firmware version for your device. 203 | The SDK version must exactly match the firmware version, otherwise you will not build a suitable kernel module. 204 | If you built the firmware yourself, instead of the SDK, you can and should use that buildroot. 205 | scripts/feeds update -a 206 | scripts/feeds install -a 207 | Copy openwrt/* to SDK folder, preserving directory structure. 208 | Copy ipobfs и ipobfs_mod (source code) to packages/ipobfs (the one there openwrt Makefile is). 209 | From SDK root run : make package/ipobfs/compile V=99 210 | Look for 2 ipk : bin/packages/..../ipobfs..ipk и bin/targets/..../kmod-ipobfs..ipk 211 | Copy selected version to the device, install via "opkg install ...ipk". 212 | If reinstalling, first "opkg remove ipobfs" / "opkg remove kmod-ipobfs". 213 | 214 | NAT break 215 | ------------ 216 | 217 | In the general case, its safe to assume that NAT can only pass tcp, udp, icmp traffic. 218 | Some NATs also contain helpers for special protocols (GRE). But not all NATs and not on all devices. 219 | NAT can pass non-standard IP protocols, but it does not have the means to track the source IP that initiated 220 | communication. If non-standard protocols work through NAT, then only work for only one device behind NAT. 221 | Using one IP protocol with more than one device behind NAT is not possible. There will be a conflict. 222 | Therefore, ipproto-xor can be used, but carefully. 223 | 224 | Consider linux-based NAT (almost all home routers) without helpers. 225 | As the study shows, transport header fields containing payload length and flags are important. 226 | Therefore, the minimum xor-data-offset for tcp is 14, for udp it is 6. Otherwise, the packet will not pass NAT at all. 227 | 228 | Any NAT will definitely follow the tcp flags, because conntrack determines the start of the connection. 229 | Conntrack is vital part of any NAT. Flags field offset in tcp header is 13. 230 | 231 | Linux conntrack by default verifies transport protocol checksums and does not track packets with invalid checksum. 232 | Such packets do not cause the appearance or change of entries in the conntrack table, the status of packets is INVALID, 233 | SNAT operation will not be applied to them, nevertheless, the forwarding of such packets will still happen unchanged, 234 | maintaining the source address from the internal network. To avoid this behavior, properly configured routers apply 235 | rules like "-m state --state INVALID -j DROP" or "-m conntrack --ctstate INVALID -j DROP", thereby prohibiting forwarding 236 | packets that conntrack refused to account. 237 | This behavior can be changed with the command "sysctl -w net.netfilter.nf_conntrack_checksum=0". 238 | In this case, the checksums will not be considered, conntrack will accept packets even with invalid cheksums, NAT will work. 239 | In openwrt, by default net.netfilter.nf_conntrack_checksum=0, so NAT works with invalid packets. 240 | But other routers usually do not change the default value, which is 1. 241 | 242 | Without exception, all NATs will correct the 2-byte checksum in tcp (offset 18) and udp (offset 6) header, 243 | since it is computed using ip source and destination. NAT changes the source ip when sending, source port 244 | can also change. To save resources, a full checksum recalculation is usually not performed. 245 | The initial checksum is taken as a basis, the difference between the initial and changed values​is added to it. 246 | The recipient receives a packet with an invalid checksum, then packet is deobfuscated by ipobfs and checksum becomes 247 | valid again, but only if the initial checksum was not changed during obfuscation, that is, 248 | data-xor-offset> = 20 for tcp and data-xor-offset> = 8 for udp. 249 | The obfuscator XORs, checksum is additive, so they are incompatible. 250 | ipobfs by default does not recalculate the checksums of transport headers, so if it is used at the receiving end, then 251 | data-xor-offset must not cover checksum field, otherwise the packet will be discarded by the system after deobfuscation 252 | As an alternative use --csum=fix option. 253 | ipobfs_mod disables checksums verification, so there is no such problem when using it. default behavior is similar to --csum=fix 254 | If ipproto_xor is used, router will not recalculate the checksum, packet will arrive with invalid checksum after deobfuscation. 255 | 256 | Many routers perform mss fix (-j TCPMSS --clamp-mss-to-pmtu or -j TCPMSS --set-mss). 257 | mss is in the tcp header options. Windows and linux send mss as the first option. The option itself takes 4 bytes. 258 | It turns out that the minimum xor-data-offset for tcp rises to 24, because bytes 22-23 can be changed by router. 259 | 260 | SUMMARY : 261 | tcp : data-xor-offset>=24 262 | udp : data-xor-offset>=8 263 | 264 | If NAT doesn’t pass packets with invalid checksums, use --csum=valid option. 265 | In terms of cpu load, it would be preferable not to use the --csum=valid mode if possible. 266 | 267 | There is information that some mobile operators terminate tcp on their servers for later proxying to the original 268 | destination. In this case, any tcp modification not at the data flow level is doomed to failure. 269 | A terminating middlebox will reject packets with a corrupted header or invalid checksum. 270 | An outgoing connection from middlebox will not repeat the same packetization as the original connection. 271 | Use obfsproxy. 272 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | English 2 | ------- 3 | 4 | see readme.eng.txt 5 | 6 | Для чего это нужно 7 | ------------------ 8 | 9 | На нас надвигается эпоха, когда повсеместно могут начать блокировать уже не отдельные IP адреса или домены, а протоколы. 10 | Одним из способов возможного преодоления сигнатурного анализа на DPI является модификация протокола. 11 | Лучший способ - модифицировать сам софт, работающий с этими протоколами. Но не всегда это бывает просто или возможно. 12 | Для TCP существует obfsproxy. Однако, в случае VPN - по TCP работают только не очень быстрые решения (openvpn). 13 | 14 | Что же делать в случае udp ? 15 | Если оба endpoint-а находятся на белом IP, то можно как следует поизвращаться прямо на уровне IP и дальше. 16 | Например, если у вас VPS, а дома роутер на openwrt, вы имеете прямой IP от провайдера, то можно применить эту технику. 17 | Если один endpoint находится за NAT, то можно как следует поизвращаться на уровне udp или tcp, 18 | не трогая IP и начало заголовков tcp/udp. 19 | 20 | Схема выглядит следующим образом : 21 | peer 1 <=> обфускатор/деобфускатор IP <=> сеть <=> обфускатор/деобфускатор IP <=> peer 2 22 | 23 | Чтобы пакет был доставлен от peer 1 до peer 2, оба имеющих белые IP, достаточно лишь иметь корректные IP заголовки. 24 | Можно выставить любой protocol number, проксорить или зашифровать IP payload, включая tcp/udp хедеры. 25 | DPI от такого может выпасть в осадок. Оно не будет понимать с чем имеет дело. 26 | Какие-то нестандартные IP протоколы с непонятно каким наполнением. 27 | Конечно, DPI может обрезать весь мусор (по его мнению), но совсем не обязательно будет это делать. 28 | Обычная программа не может сгененировать подобного типа "мусор", если только не использует raw sockets. 29 | Значит разработчики DPI вряд ли поставят себе целью ловить рыбку в "мутной воде", если только какое-то решение 30 | по обходу DPI на базе этой техники не станет достаточно популярным или белосписочность фильтров не достигнет высокого уровня. 31 | 32 | 33 | ipobfs 34 | ------ 35 | 36 | Обработчик очереди NFQUEUE, обфускатор/деобфускатор пакетов. 37 | 38 | --daemon ; демонизировать прогу 39 | --pidfile= ; сохранить PID в файл 40 | --user= ; менять uid процесса 41 | --uid=uid[:gid] ; менять uid процесса 42 | --qnum=200 ; номер очереди 43 | --debug ; вывод отладочной информации 44 | --ipproto-xor=0..255|0x00..0xFF; проксорить protocol number с указанным значением 45 | --data-xor=0xDEADBEAF ; проксорить содержимое IP payload указанным 32-битным HEX значением 46 | --data-xor-offset= ; начинать проксоривание со смещения position после IP хедера 47 | --data-xor-len= ; ксорить не более указанного количества байтов, начиная с позиции data-xor-offset 48 | --csum=none|fix|valid ; режим работы с чексуммой транспортных хедеров tcp и udp 49 | 50 | Операция xor симметрична, поэтому для обфускатора и деобфускатора задаются одни и те же параметры. 51 | На каждой стороне запускается по одному экземпляру программы. 52 | 53 | Придется немного повозиться с iptables. Отфильтровать исходящий пакет просто, потому что он 54 | идет "в открытую". На входящий пакет придется писать фильтры через u32. 55 | Номер протокола ("-p") в фильтре - это результат xor исходного протокола с ipproto-xor. 56 | 57 | server ipv4 udp:16 : 58 | 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 59 | iptables -t mangle -I POSTROUTING -o eth0 -p udp --sport 16 -j NFQUEUE --queue-num 300 --queue-bypass 60 | 61 | client ipv4 udp:16 : 62 | 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 63 | iptables -t mangle -I POSTROUTING -o eth0 -p udp --dport 16 -j NFQUEUE --queue-num 300 --queue-bypass 64 | 65 | ipobfs --qnum=300 --ipproto-xor=128 --data-xor=0x458A2ECD --data-xor-offset=4 --data-xor-len=44 66 | 67 | Почему data-xor-offset=4 : у tcp и udp в начале загловка идут номера порта источника и приемника, по 2 байта на каждый. 68 | Чтобы проще было писать u32 не трогаем номера портов. Можно и тронуть, но тогда придется вычислить что же получится 69 | после проксоривания и писать в u32 уже эти значения. 70 | Почему data-xor-len=44 : пример приведен для wireguard. 44 байта достаточно, чтобы заксорить udp header и все заголовки wireguard. 71 | Дальше идут шифрованные данные wireguard, их ксорить смысла нет. 72 | 73 | Можно даже превратить udp в "tcp мусор" при ipproto-xor=23. Согласно заголовку ip это tcp, но на месте tcp хедера мусор. 74 | Такого рода пакеты с одной стороны могут нарваться на middle-боксы, и на них сойдет с ума conntrack. 75 | С другой стороны это может оказаться даже хорошо. 76 | 77 | С ipv6 есть нюансы. В ipv6 нет понятия номера протокола. Зато есть понятие "next header". 78 | Как и в ipv4 можно туда записать все что угодно. Но на практике это может вызвать лавину ICMPv6 собщений "Type 4 - Parameter Problem". 79 | Чтобы этого избежать, надо приводить протокол к значению 59. Оно означает "no Next Header". 80 | Проксорьте 59 с номером исходного протокола, получите параметр для "ipproto-xor". 81 | Например, для udp номер протокола - 17. ipproto-xor=17^59=42 82 | Для tcp номер протокола - 6. ipproto-xor=6^59=61 83 | 84 | server ipv6 tcp:12345 : 85 | ip6tables -t mangle -I PREROUTING -i eth0 -p 59 -m u32 --u32 "40&0xFFFF=12345" -j NFQUEUE --queue-num 300 --queue-bypass 86 | ip6tables -t mangle -I POSTROUTING -o eth0 -p tcp --sport 12345 -j NFQUEUE --queue-num 300 --queue-bypass 87 | 88 | client ipv6 tcp:12345 : 89 | ip6tables -t mangle -I PREROUTING -i eth0 -p 59 -m u32 --u32 "38&0xFFFF=12345" -j NFQUEUE --queue-num 300 --queue-bypass 90 | ip6tables -t mangle -I POSTROUTING -o eth0 -p tcp --dport 12345 -j NFQUEUE --queue-num 300 --queue-bypass 91 | 92 | ipobfs --qnum=300 --ipproto-xor=61 --data-xor=0x458A2ECD --data-xor-offset=4 93 | 94 | 95 | IP ФРАГМЕНТАЦИЯ 96 | Если отсылающий хост посылает слишком длинный пакет, он фрагментируется на уровне IP. 97 | Принимающий хост собирает пакеты только адресованные самому хосту. 98 | В цепочке PREROUTING идут еще фрагментированные пакеты. При применении деобфускации 99 | только к части пакета чексумма неизбежно оказывается инвалидной. csum=fix не помогает. 100 | Для ipv4 помогает добавление правила в цепочку INPUT вместо PREROUTING. 101 | Само собой, так ловятся только пакеты, адресованные самому хосту, зато они приходят 102 | в NFQEUEUE уже собранными и корректно деобфусцируются. 103 | Фрагментация IP - нежелательное явление, с ней следует бороться выставлением корректного mtu 104 | внутри тоннеля. Есть некоторые протоколы, которые расчитывают на ip фрагментацию. К ним относится IKE (без rfc7383). 105 | 106 | IPV6 ФРАГМЕНТАЦИЯ 107 | В ipv6 тоже возможна фрагментация, однако она выполняется только отсылающим хостом, как правило только для 108 | udp и icmp, когда фрейм не влезает в mtu. Ко всем фрагментам сразу после ipv6 хедера добавляется хедер "44". 109 | К сожалению, все попытки поймать реконструированный полный фрейм в различных таблицах не приводят к успеху. 110 | Ловится только первый фрагмент. Причину так и не удалось выяснить. Баг это или фича известно разве что Торвальдсу. 111 | Так что лучший совет - не допускать фрагментации вовсе. 112 | 113 | ЧЕКСУММЫ : 114 | Работа с чексуммами начинается, когда через обфускатор проходит пакет tcp или udp. 115 | Для входящих пакетов сначала выполняется операция ipproto-xor, и уже после нее анализируется получился ли пакет tcp или udp. 116 | Для исходящих - наоборот. 117 | --csum=none - не трогать чексуммы вообще. если после деобфускации чексумма окажется инвалидной, система отбросит пакет. 118 | --csum=fix - режим игнорирования чексуммы. технически невозможно в NFQUEUE отключить проверку чексумм, потому чексумма 119 | приводится к валидному значению, чтобы система не отбросила пакет. работает только для входящих пакетов. 120 | --csum=valid - приводить чексумму к валидному состоянию для всех пакетов - входящих и исходящих. Режим полезен при работе через NAT, 121 | не пропускающие пакеты с инвалидной чексуммой. 122 | Пересчет чексумм увеличивает нагрузку на cpu. 123 | См. так же раздел "пробитие NAT". 124 | 125 | НЕДОСТАТКИ : 126 | Каждый пакет будет забрасываться в nfqueue, потому скорость значительно снизится. В 2-3 раза. 127 | Если сравнивать wireguard+ipobfs с openvpn на soho роутере, то openvpn все равно окажется медленней. 128 | 129 | 130 | ipobfs_mod 131 | ----------- 132 | 133 | То же самое, что и ipobfs, только выполнен в виде модуля ядра linux. Дает просадку производительности всего в пределах 20%. 134 | 135 | По логике функционирования дублирует ipobfs и совместим с ним. 136 | Значит можно на 1 хосте включить ipobfs, на другом ipobfs_mod, и они вместе будут работать. 137 | Однако, по умолчанию ipobfs_mod будет продуцировать tcp и udp пакеты с инвалидной чексуммой, система 138 | с ipobfs их будет отбрасывать. Используйте csum=fix на стороне ipobfs_mod. 139 | 140 | Команды iptables те же самые, только вместо направления на очередь NFQEUEUE выставляется бит в fmwark. 141 | ipobfs_mod реагирует на выставленные биты и производит обработку пакетов. 142 | 143 | Настройки передаются через параметры модуля ядра, задаваемые командой insmod. 144 | 145 | server ipv4 udp:16 : 146 | iptables -t mangle -I PREROUTING -i eth0 -p 145 -m u32 --u32 "0>>22&0x3C@0&0xFFFF=16" -j MARK --set-xmark 0x100/0x100 147 | iptables -t mangle -I POSTROUTING -o eth0 -p udp --sport 16 -j MARK --set-xmark 0x100/0x100 148 | 149 | client ipv4 udp:16 : 150 | 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 151 | iptables -t mangle -I POSTROUTING -o eth0 -p udp --dport 16 -j MARK --set-xmark 0x100/0x100 152 | 153 | rmmod ipobfs 154 | insmod /lib/modules/`uname -r`/extra/ipobfs.ko mark=0x100 ipp_xor=128 data_xor=0x458A2ECD data_xor_offset=4 data_xor_len=44 155 | 156 | Модуль поддерживает до 32 профилей. Настройки параметров для каждого профиля идут через запятую. 157 | Например, следующая команда объединит функции 2 обработчиков NFQUEUE из предыдущих примеров : 158 | 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 159 | Возможно применение разных профилей для исходящих и входящих пакетов. Так вы еще больше запутаете DPI, уменьшив корреляцию in/out потоков. 160 | Если задан параметр markmask, то профиль ищется по маске mask/markmask, в противном случае по маске mark/mark. 161 | markmask - один на все профили, через запятую перечилять для каждого профиля не нужно. 162 | Используйте markmask, когда у вас много профилей, чтобы не расходовать по биту на каждый. 163 | Например, следующий вариант может вместить 15 профилей в 4 бита : 0x10/0xf0, 0x20/0xf0, ..., 0xf0/0xf0 164 | 0x00/0xf0 означает, что пакет не обрабатывается модулем. 165 | 166 | По умолчанию модуль устанавливает хук на входящие пакеты с приоритетом mangle+1, чтобы к моменту вызова была выполнена таблица mangle. 167 | Если на вход поступают нестандартные протоколы, все в порядке. Но если идут пакеты с транспортным протоколом, в которых предусмотрена 168 | чексумма, такие как tcp или udp, то модифицированные пакеты с инвалидной чексуммой до хука mangle+1 не доходят. Модуль их не получает. 169 | Чтобы решить эту проблему, укажите параметр pre=raw и делайте : iptables -t raw -I PREROUTING ... 170 | Исходящие пакеты можно обрабатывать в обычном порядке через mangle. 171 | 172 | Если необходимо работать с фрагментированными ipv4 протоколами, замените в iptables PREROUTING на INPUT (см замечание в разделе ipobfs), 173 | укажите параметр модуля "prehook=input". 174 | 175 | Параметры pre,prehook,post,posthook задаются индивидуально для каждого профиля, поэтому если профилей несколько, их нужно перечислять 176 | через запятую. 177 | 178 | Модуль отключает проверку и подсчет чексумм на уровне ОС для обрабатываемых пакетов, в некоторых случаях пересчитывая 179 | чексуммы tcp и udp самостоятельно. 180 | Если параметр csum=none, модуль не считает суммы вообще. Сумма исходящего пакета может оказаться невалидной. 181 | При csum=fix модуль считает сумму исходящего пакета до модификации пейлоада, тем самым повторяя функции ОС 182 | или offload-а на сетевой карточке, чтобы они не сделали это на уже модифицированном пейлоаде и не испортили 2 байта данных, 183 | а передаваемый пакет после деобфускации содержал корректную чексумму. 184 | Если csum=valid, пересчет чексуммы производится после модификации пейлоада, как для исходящих, так и для входящих пакетов. 185 | Тем самым обеспечивается видимость передачи пакетов с валидной чексуммой. Коррекция чексуммы на входящем пакете необходима, 186 | если устройство с ipobfs не является получателем пакета, а выполняет функцию роутера (forward). Чтобы на выходном интерфейсе был валидный пакет. 187 | Обычный получатель не примет пакеты с инвалидной чексуммой. 188 | 189 | Параметр debug=1 включает вывод отладочной информации. Вы увидите что делается с каждым обрабатываемым пакетом в dmesg. 190 | Его стоит применять только для отладки. При большом количестве пакетов система сильно затормозится из-за избыточного вывода в dmesg. 191 | 192 | Посмотреть и изменить некоторые параметры ipobfs можно без перезагрузки модуля : /sys/module/ipobfs/parameters 193 | 194 | СБОРКА МОДУЛЯ ЯДРА на традиционной linux системе : 195 | установить заголовки ядра. для debian : 196 | sudo apt-get install linux-headers..... 197 | cd ipobfs_mod 198 | make 199 | sudo make install 200 | 201 | ЗАМЕЧАНИЕ ПО СКОРОСТИ : 202 | Если задан только ipproto-xor, замеделение можно считать нулевым. Выполняется простейшая операция изменения 1 байта. 203 | Тем не менее даже такой трюк может помочь обойти dpi. 204 | Если используется data-xor, то старайтесь ксорить только начало пакета. Если ксорение не выходит за пределы ~100-140 байт, 205 | скорее всего не придется выполнять ресурсоемкую операцию skb_linearize. Именно на ней тратится больше всего ресурсов cpu. 206 | С debug=1 можно проверить выполняется ли linearize. 207 | 208 | openwrt 209 | ------- 210 | 211 | На системе linux скачайте и распакуйте SDK от вашей версии прошивки для вашего девайса. 212 | Версия SDK должна в точности соответствовать версии прошивки, иначе вы не соберете подходящий модуль ядра. 213 | Если вы собирали прошивку самостоятельно, вместо SDK можно и нужно использовать этот buildroot. 214 | scripts/feeds update -a 215 | scripts/feeds install -a 216 | Скопируйте openwrt/* в SDK, сохраняя структуру директорий. 217 | В packages/ipobfs, там где Makefile, поместите каталоги ipobfs и ipobfs_mod с исходниками. 218 | Из корня SDK : make package/ipobfs/compile V=99 219 | Должны получиться 2 ipk : bin/packages/..../ipobfs..ipk и bin/targets/..../kmod-ipobfs..ipk 220 | Переносите их на девайс, устанавливаете через "opkg install ...ipk". Устанавливать можно только то, что вам нужно : ipobfs или kmod-ipobfs. 221 | 222 | 223 | Пробитие NAT 224 | ------------ 225 | 226 | В общем случае можно утверждать, что NAT способны пропускать лишь трафик tcp, udp, icmp. 227 | + некоторые NAT еще содержат хелперы для пропуска особых протоколов (GRE). Но не все и не на всех устройствах. 228 | NAT может пропускать нестандартные IP протоколы, но у него нет средств отследить исходный IP, который инициировал 229 | отсылку. Если нестандартные протоколы и будут работать через NAT, то только для одного устройства за NAT. 230 | Использование одного IP протокола более чем одним устройством за NAT невозможно. Будет конфликт. 231 | Учитывая этот факт, ipproto-xor применять можно, но осторожно. 232 | 233 | Рассмотрим NAT на базе linux (почти все домашние роутеры) без хелперов. 234 | Как показывает исследование, для него важны поля транспортного хедера, содержащие длину пейлоада и флаги. 235 | Поэтому, минимальный xor-data-offset для tcp - 14 , для udp - 6. Иначе пакет вовсе не пройдет NAT. 236 | 237 | Любой NAT обязательно будет следить за флагами tcp, ведь по ним conntrack определяет начало установки соединения. 238 | Без conntrack не работает ни один NAT. Смещение флагов в tcp header - 13. 239 | 240 | Linux conntrack по умолчанию проверяет чексуммы транспортных протоколов и не учитывает пакеты с инвалидной чексуммой. 241 | Такие пакеты не вызывают появление или изменение записей в таблице conntrack, статус пакетов - INVALID, операция SNAT 242 | к ним применена не будет, тем не менее форвардинг пакета все равно случится в неизменном виде, с сохранением адреса источника 243 | из внутренней сети. Чтобы этого избежать, правильно настроенные роутеры применяет правила вида 244 | "-m state --state INVALID -j DROP" или "-m conntrack --ctstate INVALID -j DROP", тем самым запрещая форвардинг 245 | пакетов, от которых отказался conntrack. 246 | Поведение можно изменить командой "sysctl -w net.netfilter.nf_conntrack_checksum=0". В этом случае чексуммы считаться не будут, 247 | conntrack будет принимать пакеты даже с инвалидной чексуммой, NAT работать будет. 248 | В openwrt по умолчанию net.netfilter.nf_conntrack_checksum=0, так что система пропускает инвалидные пакеты. 249 | Но остальные роутеры как правило не меняют значение по умолчанию, которое =1. 250 | 251 | Все без исключения NAT будут исправлять 2-байтовую чексумму в tcp (смещение 18) и udp (смещение 6), 252 | поскольку она считается с учетом ip источника и назначения. NAT меняет ip источника при отсылке, при невозможности 253 | сохранить исходный source port меняется и он. Для экономии ресурсов полный пересчет суммы обычно не производится. 254 | За основу берется исходная сумма, к ней добавляется разница между исходными и измененными значениями. 255 | К получателю приходит пакет с инвалидной суммой, далее он деобфусцируется средствами ipobfs и сумма становится валидной, 256 | если при обфускации не была затронута исходная сумма, то есть data-xor-offset>=20 для tcp и data-xor-offset>=8 для udp. 257 | Обфускатор работает по xor, checksum считается сложением, поэтому они несовместимы. 258 | ipobfs по умолчанию не пересчитывает чексуммы транспортных хедеров, поэтому если на принимающем конце используется он, то 259 | data-xor-offset необходимо делать таким, чтобы чексумма не затрагивалась, иначе пакет будет отброшен системой после деобфускации. 260 | альтернатива - использовать опцию --csum=fix 261 | ipobfs_mod отключает проверку чексумм, поэтому при его использовании этой проблемы нет. поведение по умолчанию аналогично --csum=fix 262 | Если применяется ipproto_xor, то роутер не будет пересчитывать суммы, пакет придет с инвалидной суммой после деобфускации. 263 | 264 | Большинство роутеров выполняют mss fix ( -j TCPMSS --clamp-mss-to-pmtu или -j TCPMSS --set-mss ). 265 | mss находится в опциях tcp header. Windows и linux шлют mss первой опцией. Сама опция занимает 4 байта. 266 | Выходит, минимальный xor-data-offset для tcp поднимается до 24, чтобы не трогать mss, который попортит роутер. 267 | 268 | ИТОГ : 269 | tcp : data-xor-offset>=24 270 | udp : data-xor-offset>=8 271 | 272 | Если NAT не пропускает пакеты с инвалидной чексуммой, используйте настройку --csum=valid. 273 | С точки зрения нагрузки на cpu предпочительней будет не использовать режим --csum=valid, если NAT пропускает пакеты с инвалидной чексуммой. 274 | 275 | Есть информация, что некоторые мобильные операторы производят терминацию tcp на своих серверах для последующего 276 | проксирования к точке исходного назначения. В этом случае любая модификация tcp не на уровне потока данных обречена на провал. 277 | Терминирующий middlebox отвергнет пакеты с испорченным заголовком или неверной чексуммой. 278 | Исходящее соединение от middlebox не будет повторять такое же разбиение на пакеты, как исходное соединение. 279 | Пользуйтесь obfsproxy. 280 | --------------------------------------------------------------------------------