mirror of
https://github.com/bol-van/ipobfs.git
synced 2025-12-16 20:07:08 +03:00
clear
This commit is contained in:
12
ipobfs/Makefile
Normal file
12
ipobfs/Makefile
Normal 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
118
ipobfs/checksum.c
Normal 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
12
ipobfs/checksum.h
Normal 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
615
ipobfs/ipobfs.c
Normal 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(¶ms, 0, sizeof(params));
|
||||
params.data_xor_len = 0xFFFF;
|
||||
*pidfile = 0;
|
||||
|
||||
const struct option long_options[] = {
|
||||
{"qnum",required_argument,0,0}, // optidx=0
|
||||
{"daemon",no_argument,0,0}, // optidx=1
|
||||
{"pidfile",required_argument,0,0}, // optidx=2
|
||||
{"user",required_argument,0,0 },// optidx=3
|
||||
{"uid",required_argument,0,0 },// optidx=4
|
||||
{"debug",no_argument,0,0 },// optidx=5
|
||||
{"ipproto-xor",required_argument,0,0}, // optidx=6
|
||||
{"data-xor",required_argument,0,0}, // optidx=7
|
||||
{"data-xor-offset",required_argument,0,0}, // optidx=8
|
||||
{"data-xor-len",required_argument,0,0}, // optidx=9
|
||||
{"csum",required_argument,0,0}, // optidx=10
|
||||
{NULL,0,NULL,0}
|
||||
};
|
||||
if (argc < 2) exithelp();
|
||||
while ((v = getopt_long_only(argc, argv, "", long_options, &option_index)) != -1)
|
||||
{
|
||||
if (v) exithelp();
|
||||
switch (option_index)
|
||||
{
|
||||
case 0: /* qnum */
|
||||
params.qnum = atoi(optarg);
|
||||
if (params.qnum < 0 || params.qnum>65535)
|
||||
{
|
||||
fprintf(stderr, "bad qnum\n");
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
case 1: /* daemon */
|
||||
daemon = true;
|
||||
break;
|
||||
case 2: /* pidfile */
|
||||
strncpy(pidfile, optarg, sizeof(pidfile));
|
||||
pidfile[sizeof(pidfile) - 1] = '\0';
|
||||
break;
|
||||
case 3: /* user */
|
||||
{
|
||||
struct passwd *pwd = getpwnam(optarg);
|
||||
if (!pwd)
|
||||
{
|
||||
fprintf(stderr, "non-existent username supplied\n");
|
||||
exit(1);
|
||||
}
|
||||
uid = pwd->pw_uid;
|
||||
gid = pwd->pw_gid;
|
||||
break;
|
||||
}
|
||||
case 4: /* uid */
|
||||
gid = 0x7FFFFFFF; // default git. drop gid=0
|
||||
if (!sscanf(optarg, "%u:%u", &uid, &gid))
|
||||
{
|
||||
fprintf(stderr, "--uid should be : uid[:gid]\n");
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
case 5: /* debug */
|
||||
params.debug = true;
|
||||
break;
|
||||
case 6: /* ipproto-xor */
|
||||
{
|
||||
uint u;
|
||||
if (!sscanf(optarg, "0x%X", &u) && !sscanf(optarg, "%u", &u) || u > 255)
|
||||
{
|
||||
fprintf(stderr, "ipp-xor should be 1-byte decimal or 0x<HEX>\n");
|
||||
exit(1);
|
||||
}
|
||||
params.ipp_xor = (uint8_t)u;
|
||||
}
|
||||
break;
|
||||
case 7: /* data-xor */
|
||||
if (!sscanf(optarg, "0x%X", ¶ms.data_xor))
|
||||
{
|
||||
fprintf(stderr, "data-xor should be 32 bit HEX starting with 0x\n");
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
case 8: /* data-xor-offset */
|
||||
params.data_xor_offset = (size_t)atoi(optarg);
|
||||
break;
|
||||
case 9: /* data-xor-len */
|
||||
params.data_xor_len = (size_t)atoi(optarg);
|
||||
break;
|
||||
case 10: /* csum */
|
||||
if (!strcmp(optarg, "none"))
|
||||
params.csum = none;
|
||||
else if (!strcmp(optarg, "fix"))
|
||||
params.csum = fix;
|
||||
else if (!strcmp(optarg, "valid"))
|
||||
params.csum = valid;
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "invalid csum parameter\n");
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (daemon) daemonize();
|
||||
|
||||
h = NULL;
|
||||
qh = NULL;
|
||||
|
||||
if (*pidfile && !writepid(pidfile))
|
||||
{
|
||||
fprintf(stderr, "could not write pidfile\n");
|
||||
goto exiterr;
|
||||
}
|
||||
|
||||
printf("opening library handle\n");
|
||||
h = nfq_open();
|
||||
if (!h) {
|
||||
fprintf(stderr, "error during nfq_open()\n");
|
||||
goto exiterr;
|
||||
}
|
||||
|
||||
printf("unbinding existing nf_queue handler for AF_INET (if any)\n");
|
||||
if (nfq_unbind_pf(h, AF_INET) < 0) {
|
||||
fprintf(stderr, "error during nfq_unbind_pf()\n");
|
||||
goto exiterr;
|
||||
}
|
||||
|
||||
printf("binding nfnetlink_queue as nf_queue handler for AF_INET\n");
|
||||
if (nfq_bind_pf(h, AF_INET) < 0) {
|
||||
fprintf(stderr, "error during nfq_bind_pf()\n");
|
||||
goto exiterr;
|
||||
}
|
||||
|
||||
printf("binding this socket to queue '%u'\n", params.qnum);
|
||||
qh = nfq_create_queue(h, params.qnum, &cb, NULL);
|
||||
if (!qh) {
|
||||
fprintf(stderr, "error during nfq_create_queue()\n");
|
||||
goto exiterr;
|
||||
}
|
||||
|
||||
printf("setting copy_packet mode\n");
|
||||
if (nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff) < 0) {
|
||||
fprintf(stderr, "can't set packet_copy mode\n");
|
||||
goto exiterr;
|
||||
}
|
||||
|
||||
if (!droproot(uid, gid)) goto exiterr;
|
||||
fprintf(stderr, "Running as UID=%u GID=%u\n", getuid(), getgid());
|
||||
|
||||
fd = nfq_fd(h);
|
||||
while ((rv = recv(fd, buf, sizeof(buf), 0)) && rv >= 0)
|
||||
{
|
||||
int r = nfq_handle_packet(h, buf, rv);
|
||||
if (r) fprintf(stderr, "nfq_handle_packet error %d\n", r);
|
||||
}
|
||||
|
||||
printf("unbinding from queue 0\n");
|
||||
nfq_destroy_queue(qh);
|
||||
|
||||
#ifdef INSANE
|
||||
/* normally, applications SHOULD NOT issue this command, since
|
||||
* it detaches other programs/sockets from AF_INET, too ! */
|
||||
printf("unbinding from AF_INET\n");
|
||||
nfq_unbind_pf(h, AF_INET);
|
||||
#endif
|
||||
|
||||
printf("closing library handle\n");
|
||||
nfq_close(h);
|
||||
|
||||
return 0;
|
||||
|
||||
exiterr:
|
||||
if (qh) nfq_destroy_queue(qh);
|
||||
if (h) nfq_close(h);
|
||||
return 1;
|
||||
}
|
||||
19
ipobfs_mod/Makefile
Normal file
19
ipobfs_mod/Makefile
Normal 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
549
ipobfs_mod/ipobfs.c
Normal 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
47
iptables.txt
Normal 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
50
mod_hooks.txt
Normal 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,
|
||||
};
|
||||
51
openwrt/package/ipobfs/Makefile
Normal file
51
openwrt/package/ipobfs/Makefile
Normal 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
271
readme.eng.txt
Normal 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 doesn’t pass packets with invalid checksums, use --csum=valid option.
|
||||
In terms of cpu load, it would be preferable not to use the --csum=valid mode if possible.
|
||||
|
||||
There is information that some mobile operators terminate tcp on their servers for later proxying to the original
|
||||
destination. In this case, any tcp modification not at the data flow level is doomed to failure.
|
||||
A terminating middlebox will reject packets with a corrupted header or invalid checksum.
|
||||
An outgoing connection from middlebox will not repeat the same packetization as the original connection.
|
||||
Use obfsproxy.
|
||||
279
readme.txt
Normal file
279
readme.txt
Normal 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.
|
||||
Reference in New Issue
Block a user