This commit is contained in:
bol-van
2025-04-04 09:44:20 +03:00
commit 02896c5008
11 changed files with 2023 additions and 0 deletions

12
ipobfs/Makefile Normal file
View File

@@ -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

118
ipobfs/checksum.c Normal file
View File

@@ -0,0 +1,118 @@
#include "checksum.h"
#include <netinet/in.h>
//#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);
}

12
ipobfs/checksum.h Normal file
View File

@@ -0,0 +1,12 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <netinet/ip6.h>
#include <netinet/ip.h>
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);

615
ipobfs/ipobfs.c Normal file
View File

@@ -0,0 +1,615 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdint.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <libnetfilter_queue/libnetfilter_queue.h>
#include <getopt.h>
#include <fcntl.h>
#include <pwd.h>
#include <sys/capability.h>
#include <sys/prctl.h>
#include <errno.h>
#include <time.h>
#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<len)
{
size_t end = ((data_xor_offset+data_xor_len)<(data_pos+len)) ? data_xor_offset+data_xor_len-data_pos : len;
uint32_t xor,n;
len = end-start;
data += start;
xor = data_xor;
n = (4-((data_pos+start)&3))&3;
if (n) xor=rotr32(xor,n<<3);
while(len && ((size_t)data & 7))
{
*data++ ^= (uint8_t)(xor=rotl32(xor,8));
len--;
}
{
register uint64_t nxor=htonl(xor);
nxor = (nxor<<32) | nxor;
for( ; len>=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=<nfqueue_number>\n"
" --daemon\t\t\t; daemonize\n"
" --pidfile=<filename>\t\t; write pid to file\n"
" --user=<username>\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=<position>\t; start xoring at specified position after IP header end\n"
" --data-xor-len=<bytes>\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(&params, 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<HEX>\n");
exit(1);
}
params.ipp_xor = (uint8_t)u;
}
break;
case 7: /* data-xor */
if (!sscanf(optarg, "0x%X", &params.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;
}

19
ipobfs_mod/Makefile Normal file
View File

@@ -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

549
ipobfs_mod/ipobfs.c Normal file
View File

@@ -0,0 +1,549 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/netfilter_ipv6.h>
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <net/ip6_checksum.h>
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 <integer>");
MODULE_PARM_DESC(posthook, "output hook : none, postrouting (default), output, forward");
MODULE_PARM_DESC(post, "output hook priority : mangle (default), raw, filter or <integer>");
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) (idx<ct_##name ? name[idx] : 0)
#define GET_DATA_XOR_LEN(idx) (GET_PARAM(data_xor_len,idx) ? GET_PARAM(data_xor_len,idx) : 0xFFFF)
typedef struct {
int priority;
bool bOutgoing;
} t_hook_id;
static int nf_priority_from_string(const char *s)
{
int r,n = NF_IP_PRI_MANGLE+1;
if (s)
{
if (!strcmp(s,"mangle"))
n = NF_IP_PRI_MANGLE+1;
else if (!strcmp(s,"raw"))
n = NF_IP_PRI_RAW+1;
else if (!strcmp(s,"filter"))
n = NF_IP_PRI_FILTER+1;
else
r = kstrtoint(s, 0, &n);
}
return n;
}
static const char *nf_string_from_priority(int pri)
{
switch(pri)
{
case NF_IP_PRI_RAW+1: return "raw";
case NF_IP_PRI_MANGLE+1: return "mangle";
case NF_IP_PRI_FILTER+1: return "filter";
default: return "custom";
}
}
static unsigned int nf_hooknum_from_string(const char *s, int def_num)
{
int r,n = def_num;
if (s)
{
if (!strcmp(s,"input"))
n = NF_INET_LOCAL_IN;
else if (!strcmp(s,"forward"))
n = NF_INET_FORWARD;
else if (!strcmp(s,"output"))
n = NF_INET_LOCAL_OUT;
else if (!strcmp(s,"postrouting"))
n = NF_INET_POST_ROUTING;
else if (!strcmp(s,"prerouting"))
n = NF_INET_PRE_ROUTING;
else if (!strcmp(s,"none"))
n = -1;
else
r = kstrtoint(s, 0, &n);
}
return n;
}
static const char *nf_string_from_hooknum(int hooknum)
{
switch(hooknum)
{
case NF_INET_PRE_ROUTING: return "prerouting";
case NF_INET_POST_ROUTING: return "postrouting";
case NF_INET_LOCAL_IN: return "input";
case NF_INET_LOCAL_OUT: return "output";
case NF_INET_FORWARD: return "forward";
case -1: return "none";
default: return "custom";
}
}
static t_csum csum_from_string(char *s)
{
t_csum m;
if (!s) m=none;
else if (!strcmp(s,"fix")) m=fix;
else if (!strcmp(s,"valid")) m=valid;
else m=none;
return m;
}
static const char *string_from_csum(t_csum csum)
{
switch(csum)
{
case fix: return "fix";
case valid: return "valid";
default: return "none";
}
}
static void translate_csum_s(void)
{
int i;
for(i=0;i<ct_csum;i++) csum[i]=csum_from_string(csum_s[i]);
}
static void translate_hooknum(char **hooknum_s, int ct, unsigned int *hooknum, int def_hooknum)
{
int i;
for(i=0;i<ct_mark;i++) hooknum[i] = nf_hooknum_from_string(i<ct ? hooknum_s[i] : NULL, def_hooknum);
}
static void translate_priority(char **pri_s, int ct, int *pri)
{
int i;
for(i=0;i<ct_mark;i++) pri[i] = nf_priority_from_string(i<ct ? pri_s[i] : NULL);
}
static int find_mark(uint fwmark)
{
int i;
if (markmask)
{
uint m = fwmark & markmask;
for(i=0;i<ct_mark;i++)
if (m == mark[i]) return i;
}
else
{
for(i=0;i<ct_mark;i++)
if (fwmark & mark[i]) return i;
}
return -1;
}
static void ip4_fix_checksum(struct iphdr *ip)
{
ip->check = 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 (tlen<sizeof(struct tcphdr)) return;
check_old = ((struct tcphdr*)pt)->check;
((struct tcphdr*)pt)->check = 0;
break;
case IPPROTO_UDP:
if (tlen<sizeof(struct udphdr)) return;
check_old = ((struct udphdr*)pt)->check;
((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<len)
{
uint end = ((data_xor_offset+data_xor_len)<(data_pos+len)) ? data_xor_offset+data_xor_len-data_pos : len;
u32 xor,n;
len = end-start;
data += start;
xor = data_xor;
n = (4-((data_pos+start)&3))&3;
if (n) xor=rotr32(xor,n<<3);
while(len && ((size_t)data & 7))
{
*data++ ^= (u8)(xor=rotl32(xor,8));
len--;
}
{
register u64 nxor=htonl(xor);
nxor = (nxor<<32) | nxor;
for( ; len>=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<ct;i++)
if (nfhk[i].hooknum==hooknum && nfhk[i].priority==priority && nfhk[i].pf==pf)
return i;
return -1;
}
static void fill_hook_table(struct nf_hook_ops *nfhk, int *ct, t_hook_id *hookid, unsigned int *hooknums, int *pris, bool bOutgoing)
{
int i, n;
*ct = 0;
for(i=n=0;i<ct_mark;i++)
{
if (hooknums[i]!=-1 && find_hook(nfhk,*ct,hooknums[i],pris[i],PF_INET)==-1)
{
hookid[n].priority = pris[i];
hookid[n].bOutgoing = bOutgoing;
nfhk[*ct].hook = hook_ip;
nfhk[*ct].hooknum = hooknums[i];
nfhk[*ct].priority = pris[i];
nfhk[*ct].priv = hookid+n;
nfhk[*ct].pf = PF_INET;
nfhk[*ct+1] = nfhk[*ct];
nfhk[*ct+1].pf = PF_INET6;
*ct+=2;
n++;
}
}
}
static void printk_hook_table(const char *prefix, const struct nf_hook_ops *nfhk, int ct)
{
int i;
printk(KERN_INFO "ipobfs: registered %s hooks:\n",prefix);
for(i=0;i<ct;i++)
{
if (nfhk[i].pf==PF_INET)
printk(KERN_INFO "ipobfs: hook=%s(%d) priority=%s(%d)\n",
nf_string_from_hooknum(nfhk[i].hooknum),nfhk[i].hooknum,
nf_string_from_priority(nfhk[i].priority),nfhk[i].priority);
}
}
int init_module(void)
{
int i;
if (!ct_mark)
{
printk(KERN_ERR "ipobfs: this module requires parameters. at least one profile is required. use 'mark' parameter\n");
return -EINVAL;
}
translate_csum_s();
translate_hooknum(prehook_s,ct_prehook,prehook,NF_INET_PRE_ROUTING);
translate_priority(pre_s,ct_pre,pre);
translate_hooknum(posthook_s,ct_posthook,posthook,NF_INET_POST_ROUTING);
translate_priority(post_s,ct_post,post);
printk(KERN_INFO "ipobfs: module loaded : debug=%d ct_mark=%d markmask=%08X ct_ipp_xor=%d ct_data_xor=%d ct_data_xor_offset=%d ct_csum=%d\n",
debug,ct_mark,markmask,ct_ipp_xor,ct_data_xor,ct_data_xor_offset,ct_csum);
for (i=0;i<ct_mark;i++) printk(KERN_INFO "ipobfs: mark 0x%08X/0x%08X : ipp_xor=%u(0x%02X) data_xor=0x%08X data_xor_offset=%u data_xor_len=%u csum=%s prehook=%s(%d) pre=%s(%d) posthook=%s(%d) post=%s(%d)\n",
GET_PARAM(mark,i),markmask ? markmask : GET_PARAM(mark,i),
GET_PARAM(ipp_xor,i),GET_PARAM(ipp_xor,i),GET_PARAM(data_xor,i),GET_PARAM(data_xor_offset,i),GET_PARAM(data_xor_len,i),
string_from_csum(GET_PARAM(csum,i)),
nf_string_from_hooknum(prehook[i]),prehook[i],
nf_string_from_priority(pre[i]),pre[i],
nf_string_from_hooknum(posthook[i]),posthook[i],
nf_string_from_priority(post[i]),post[i]);
fill_hook_table(nfhk_pre,&ct_nfhk_pre,hookid_pre,prehook,pre,false);
i = nf_register_net_hooks(&init_net,nfhk_pre,ct_nfhk_pre);
if (i)
{
printk(KERN_ERR "ipobfs: could not register netfilter pre hooks. err=%d\n",i);
return i;
}
fill_hook_table(nfhk_post,&ct_nfhk_post,hookid_post,posthook,post,true);
i = nf_register_net_hooks(&init_net,nfhk_post,ct_nfhk_post);
if (i)
{
nf_unregister_net_hooks(&init_net,nfhk_pre,ct_nfhk_pre);
printk(KERN_ERR "ipobfs: could not register netfilter post hooks. err=%d\n",i);
return i;
}
printk_hook_table("pre",nfhk_pre,ct_nfhk_pre);
printk_hook_table("post",nfhk_post,ct_nfhk_post);
return 0;
}
void cleanup_module(void)
{
nf_unregister_net_hooks(&init_net,nfhk_pre,ct_nfhk_pre);
nf_unregister_net_hooks(&init_net,nfhk_post,ct_nfhk_post);
printk(KERN_INFO "ipobfs: module unloaded\n");
}

47
iptables.txt Normal file
View File

@@ -0,0 +1,47 @@
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
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

50
mod_hooks.txt Normal file
View File

@@ -0,0 +1,50 @@
ipobfs_mod sets up 2 hooks : one for inbound, one for outbound
module takes parameters
pre=<priority> (default : mangle)
prehook=<hooknum> (default : prerouting)
post=<priority> (default : mangle)
posthook=<hooknum> (default : postrouting)
priority : mangle (default), raw, filter or <integer>
raw = NF_IP_PRI_RAW+1
mangle = NF_IP_PRI_MANGLE+1
filter = NF_IP_PRI_FILTER+1
<integer> = positive or negative decimal or hex value. hex start with 0x prefix
hooknum : prerouting (inbound default), input, output, forward, postrouting (outbound default) or <integer>
prerouting = NF_INET_PRE_ROUTING
input = NF_INET_LOCAL_IN
forward = NF_INET_FORWARD
output = NF_INET_LOCAL_OUT
postrouting = NF_INET_POST_ROUTING
<integer> = 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,
};

View File

@@ -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))

271
readme.eng.txt Normal file
View File

@@ -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=<nfqueue_number>
--daemon ; daemonize
--pidfile=<filename> ; write pid to file
--user=<username> ; 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=<position> ; start xoring at specified position after IP header end
--data-xor-len=<bytes> ; 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 valuesis 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 doesnt 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.

279
readme.txt Normal file
View File

@@ -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=<file> ; сохранить PID в файл
--user=<username> ; менять 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> ; начинать проксоривание со смещения position после IP хедера
--data-xor-len=<bytes> ; ксорить не более указанного количества байтов, начиная с позиции 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.