--------------------------Raw TCP/IP packetz-------------------------- ...with a side of kernel modules
Hey, kidz! How you doin`?
Ready to be diving into the dark corners of networking Linux? You are?! Good, good!
That`s the spirit!
As promised last time, it`s time to have a look at IP headers (and TCP headers..and
raw TCP/IP packetz, sockets! all their friends!). Yes, we are starting to go up on
the OSI Layers (but you`ll see, on Linux system that means to go waaaay down the rabbit
hole, just to catch a glimpse of what`s going on...or any other OS)
[-------------------Where am I?!
We are here, right now:
it goes this way --->
+----------------+----------------+------------------+------------+----------+
OSI | Layer1 | Layer2 | Layer3 | Layer4 | Layer5 |
Layers | Physical | Data Link | Network | Transport | Session |
+----------------+----------------+------------------+------------+----------+
+----------------+----------------+------------------+-----------------------+
TCP/IP | Transmission | Physical | Network | Transport |
| Medium | protocol | | Sockets/Streams |
| (Fiber, etc) | (Ethernet) |IP+ARP/RARP/ICMP | TCP | UDP |
+----------------+----------------+------------------+-----------------------+
So, as usual, hold on to yer delicious cookies and strong coffee,
thou shalt be educated today!
[-------------------Educational stuff
[1.] [The] RAW[r of] TCP/IP packets
This is easy peasy, since I am really good at math:
TCP packet = IP header + TCP header + data
VoilĂ ! Oh, yeah, and pay attention to the pseudo Header in TCP checksum.
Let`s see how they look like:
IP HEADER
00 | __ | __ | __ | __ | __ | __ | 07 |
__ | __ | __ | __ | __ | __ | __ | 15 |
__ | __ | __ | __ | __ | __ | __ | 23 |
__ | __ | __ | __ | __ | __ | __ | 31 |
Version | IHL | Type Of Service | Total Length |
Identification | Flags | Fragment Offset |
Time To Live | Protocol | Header Checksum |
Source Address |
Destination Address |
Options | Padding |
TCP HEADER
PSEUDO HEADER
00 | __ | __ | __ | __ | __ | __ | 07 |
__ | __ | __ | __ | __ | __ | __ | 15 |
__ | __ | __ | __ | __ | __ | __ | 23 |
__ | __ | __ | __ | __ | __ | __ | 31 |
Source IP address |
Destination IP address |
0 |
IP Protocol |
Total length |
Do we need to write code for all that?
Well, kidz...life is easy, not simple!
Any new structures?!
Yes, and quite nasty ones!
[a] struct tcphdr
struct tcphdr {
u_short th_sport; /* source port */
u_short th_dport; /* destination port */
tcp_seq th_seq; /* sequence number */
tcp_seq th_ack; /* acknowledgement number */
#if BYTE_ORDER == LITTLE_ENDIAN
u_char th_x2:4, /* (unused) */
th_off:4; /* data offset */
#endif
#if BYTE_ORDER == BIG_ENDIAN
u_char th_off:4, /* data offset */
th_x2:4; /* (unused) */
#endif
u_char th_flags;
#define TH_FIN 0x01
#define TH_SYN 0x02
#define TH_RST 0x04
#define TH_PUSH 0x08
#define TH_ACK 0x10
#define TH_URG 0x20
u_short th_win; /* window */
u_short th_sum; /* checksum */
u_short th_urp; /* urgent pointer */
};
[b] struct iphdr
struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
__u8 ihl:4,
version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
__u8 version:4,
ihl:4;
#else
#error "Please fix "
#endif
__u8 tos;
__be16 tot_len;
__be16 id;
__be16 frag_off;
__u8 ttl;
__u8 protocol;
__sum16 check;
__be32 saddr;
__be32 daddr;
/*The options start here. */
};
That little endian &big endian represent the byte order.
!!! This is your homework, to learn about endianness
As a little helper that I am, an example of how to test the endianess:
#include<stdio.h>
#include<string.h>
#define eh "!"
int main (int argc, char **argv)
{
FILE* file;
struct {
int first;
char last[4];
} order;
order.first = 0x41424344;
strcpy(order.last, eh );
file = fopen ("checkme", "wb");
if (file) {
fwrite (&order, sizeof (order), 1, file);
fclose (file);
}
}
Compile, run & check:
root@f8918abfeb55:/home/ipy# gcc endy.c -o endy
root@f8918abfeb55:/home/ipy# ./endy
root@f8918abfeb55:/home/ipy#
root@f8918abfeb55:/home/ipy# hexdump -e '8/1 "|%02X ""|\t\t"" -> "' -e '5/1 " | %c""|\n"' checkme
|44 |43 |42 |41 |21 |00 |00 |00| -> | D | C | B | A | !|
See how you learn new things, just by questioning everything?!
I have added a few documentation links in the code, as comments (because I am evil like that).
So no worries, you'll have a bunch to read.
Give us the code! Give us the precious!
[-------------------le wild example
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<linux/ip.h>
#include<linux/tcp.h>
/******
*
* based on a true horror story:
* https://jve.linuxwall.info/ressources/code/forgetcp.c
*
********************************/
#define DATA "nullQWasHere"
#define IPHSIZE 20
#define TCPHSIZE 20
#define PSEUDOTCPHSIZE 12
/* generic calculation of checksum */
/* !!!Do you ever read the stuff I give ya?! */
/* Pseudo header & chksum */
/* http://www.tcpipguide.com/free/t_TCPChecksumCalculationandtheTCPPseudoHeader-2.htm */
static unsigned short cksum(unsigned short *ptr,int nbytes)
{
register long sum;
unsigned short oddbyte;
register short answer;
sum=0;
while(nbytes>1) {
sum+=*ptr++;
nbytes-=2;
}
if(nbytes==1) {
oddbyte=0;
*((unsigned char *)&oddbyte)=*(unsigned char *)ptr;
sum+=oddbyte;
}
sum = (sum>>16)+(sum & 0xffff);
sum = sum + (sum>>16);
answer=(short)~sum;
return(answer);
}
struct pseudo_header {
uint32_t s_addr;
uint32_t d_addr;
uint8_t nil;
uint8_t IP_protocol;
uint16_t tot_Len;
};
int main(int argc, char **argv) {
int sock, bytes, one = 1;
struct iphdr *ip_h;
struct tcphdr *tcp_h;
char *s_ip =argv[1];
char *d_ip = argv[2];
int d_port = 8667;
int s_port = 8666;
/*
* K'chin'! http://packetlife.net/blog/2010/jun/7/understanding-tcp-sequence-acknowledgment-numbers/
* */
uint32_t TCPSeqAck = 294967;
char *data;
char p[512];
struct sockaddr_in addr_in;
struct pseudo_header pseudo_tcp;
char *pseudo_p;
if((sock = socket(PF_INET, SOCK_RAW, IPPROTO_RAW)) < 0)
{
perror("Error while creating socket");
exit(-1);
}
/*The IPv4 layer generates an IP header when sending a packet */
/*unless the IP_HDRINCL socket option is enabled on the socket. */
/*When it is enabled, the p must contain an IP header. */
/*For receiving the IP header is always included in the packet */
if(setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char *)&one, sizeof(one)) < 0)
{
perror("Error while setting socket options");
exit(-1);
}
addr_in.sin_family = AF_INET;
addr_in.sin_port = htons(d_port);
addr_in.sin_addr.s_addr = inet_addr(d_ip);
memset(p, 0, sizeof(p));
ip_h = (struct iphdr *) p;
tcp_h = (struct tcphdr *) (p + sizeof(struct iphdr));
data = (char *) (p + sizeof(struct iphdr) + sizeof(struct tcphdr));
strcpy(data, DATA);
//Populate ip_h
ip_h->ihl=IPHSIZE>>2;
ip_h->version = 4;
ip_h->tos = 0;
ip_h->tot_len = IPHSIZE + TCPHSIZE+strlen(data);
ip_h->id = htons(rand()%65535);
ip_h->frag_off = 0x00;
ip_h->ttl = 0xFF;
ip_h->protocol = IPPROTO_TCP;
ip_h->check = 0;
ip_h->saddr = inet_addr(s_ip);
ip_h->daddr = inet_addr(d_ip);
ip_h->check = cksum((unsigned short *) p, ip_h->tot_len);
//Populate tcp_h
tcp_h->source = htons(s_port);
tcp_h->dest = htons(d_port);
tcp_h->seq = 0x0;
tcp_h->ack_seq = 0x0;
tcp_h->doff = TCPHSIZE >> 2;
tcp_h->res1 = 0;
/*do populate more from here..ahahahaha!It still works, tho!*/
//checksum for the TCP header
pseudo_tcp.s_addr = inet_addr(argv[1]);
pseudo_tcp.d_addr = inet_addr(argv[2]);
pseudo_tcp.nil = 0;
pseudo_tcp.IP_protocol = IPPROTO_TCP;
pseudo_tcp.tot_Len=htons(sizeof(struct tcphdr) + strlen(data));
pseudo_p = (void *)malloc((int) (sizeof(struct pseudo_header) + sizeof(struct tcphdr) + strlen(data)));
memset(pseudo_p, 0, sizeof(struct pseudo_header) + sizeof(struct tcphdr) + strlen(data));
memcpy(pseudo_p, (void *) &pseudo_tcp, sizeof(struct pseudo_header));
tcp_h->seq = htonl(TCPSeqAck);
tcp_h->check = 0;
memcpy(pseudo_p + sizeof(struct pseudo_header), tcp_h, sizeof(struct tcphdr) + strlen(data));
tcp_h->check = (cksum((unsigned short *) pseudo_p, (int) (sizeof(struct pseudo_header) +
sizeof(struct tcphdr) + strlen(data))));
//release the packet!!
if((bytes = sendto(sock, p, ip_h->tot_len, 0, (struct sockaddr *) &addr_in, sizeof(addr_in))) < 0) {
perror("Error on sendto()");
}
else {
printf("Success! Sent %d bytes.\n", bytes);
}
//...sheesh!
return 0;
}
Compile as usual:
root@f8918abfeb55:/home/ticy#gcc tiny_tcp.c -o tiny_tcp
...and run:
root@f8918abfeb55:/home/ticy#./tiny_tcp 172.17.0.2 172.17.0.2
From a different terminal, run below tcpdump command inside container:
root@f8918abfeb55:/home# tcpdump -vv -X port 8667
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
02:07:37.273349 IP (tos 0x0, ttl 255, id 45298, offset 0, flags [none], proto TCP (6), length 52)
172.17.0.2.8666 > 172.17.0.2.8667: Flags [none], cksum 0x4b4a (correct), seq 294967:294979, win 0, length 12
0x0000: 4500 0034 b0f2 0000 ff06 b2aa ac11 0002 E..4............
0x0010: ac11 0002 21da 21db 0004 8037 0000 0000 ....!.!....7....
0x0020: 5000 0000 4b4a 0000 6e75 6c6c 5157 6173 P...KJ..nullQWas
0x0030: 4865 7265 Here
[2] ...then packetZ met the Kernel modules &made a weird sound like a "Spoof!"
Now it's the right moment when you can fight or flight. I suggest you stick around.
Kernel modules are pretty awesome.
Also, we`ll be learning how to load kernel modules inside containers *winks-winks*
I know you are very found of "ahaha" docker's image.
We will be using its image ID to be create new container with the necessary kernel
libraries:
All you gotta do, is run the following command:
nullQ@tr0n$ sudo docker run --privileged --cap-add=ALL -d -v /dev:/dev \
-v /lib/modules:/lib/modules -v /usr/src:/usr/src -ti fb0c87621d06
where fb0c87621d06 is the image ID for ahaha
root@tr0n:~#docker images | grep -i aha | awk {'print$1, $3'}
ahaha fb0c87621d06
For the new container, make sure you update the gcc version:
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 60 \
--slave /usr/bin/g++ g++ /usr/bin/g++-5
Then, perform an "apt-get install build-essential" and "apt-get update" ,
and you should be all set-up... and install "make"
This is how my container looks like:
root@ec0adc35856f:/home/22kery# more /proc/version
Linux version 4.13.0-32-generic (buildd@lgw01-amd64-004) (gcc version 5.4.0 20160609 (Ub
untu 5.4.0-6ubuntu1~16.04.5)) #35~16.04.1-Ubuntu SMP Thu Jan 25 10:13:43 UTC 2018
[3.] "This is where the fun begins!"
...also, do check this awesome tutorial on Kernel modules: CLICK ME!!! WIN BILLION DOLLARS IN A SEC!!!!"
It might be ol', but it still has a lot of potential for hungry minds.
How does that help us?
Once you find out its real potential, you`ll be really hook-ed! AHAHA!
(oh, gawd, i'm laughing at my own bad jokes)
Now kidz, in order to proceed further, let's talk about skb.
SKB is the fundamental data structure in the Linux networking code,and it's a socket buffer
(struct skb_buff)
This SKB receives or sends every packet.
...and let's check a few functions specific to skb_buff:
#ifdef NET_SKBUFF_DATA_USES_OFFSET
static inline unsigned char *skb_network_header(const struct sk_buff *skb)
{
return skb->head + skb->network_header;
}
static inline unsigned char *skb_mac_header(const struct sk_buff *skb)
{
return skb->head + skb->mac_header;
}
#else /* NET_SKBUFF_DATA_USES_OFFSET */
static inline unsigned char *skb_network_header(const struct sk_buff *skb)
{
return skb->network_header;
}
static inline unsigned char *skb_mac_header(const struct sk_buff *skb)
{
return skb->mac_header;
}
#endif /* NET_SKBUFF_DATA_USES_OFFSET */
[-------------------and example time...again!
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/netfilter.h>
#include<linux/netfilter_ipv4.h>
#include<linux/skbuff.h>
#include<linux/udp.h>
#include<linux/icmp.h>
#include<linux/ip.h>
#include<linux/inet.h>
#define device "eth0"
static struct nf_hook_ops nfin;
static unsigned int hook_func_in(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
struct ethhdr *eth;
struct iphdr *ip_header;
if (!device)
return NF_ACCEPT;
eth = (struct ethhdr*)skb_mac_header(skb);
ip_header = (struct iphdr *)skb_network_header(skb);
printk(KERN_INFO "src mac %pM, dst mac %pM\n", eth->h_source, eth->h_dest);
printk(KERN_INFO "src IP addr: %pI4\n", &ip_header->saddr);
return NF_ACCEPT;
}
/* On older vesion than 4.13 kernel (or newer version than 4.13)
netfilter function has the form:
unsigned int hook_func_in(unsigned int hooknum,
struct sk_buff **skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *)) */
static int __init init_main(void)
{
nfin.hook = hook_func_in;
nfin.hooknum = NF_INET_PRE_ROUTING;
nfin.pf = PF_INET;
nfin.priority = NF_IP_PRI_FIRST;
nf_register_net_hook(&init_net, &nfin);
//on older versions than 4.13 kernel
//or newer version than 4.13 kernel
//use: nf_register_hook(&nfin)
return 0;
}
static void __exit cleanup_main(void)
{
nf_unregister_net_hook(&init_net, &nfin);
//on older versions than 4.13 kernel
//or newer version than 4.13 kernel
//use: nf_unregister_hook(&nfin)
}
module_init(init_main);
module_exit(cleanup_main);
What else?!
Oh, I almost forgot the most important part: Nefilter Modules
Also, do create a new folder, and a Makefile, with following content:
root@ec0adc35856f:/home/22kerny#more Makefile
obj-m := kerny.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
clean:
rm *o
My 22kerny folder contains two files, only: the program kerny.c and Makefile
Build the module, and load it:
root@ec0adc35856f:/home/22kerny# make
root@ec0adc35856f:/home/22kerny# insmod kerny.ko
..and now what?! Nothing happened!
O'rilly?!
K`mon, let`s test it.
Disconnect from your neighbor's wifi for a minute (no extra packets to be intercepted),
and check the output.
Resend the raw packet we have just crafted in the other container:
root@f8918abfeb55:/home/ipy# ./tiny_tcp 172.17.0.2 172.17.0.3
..and now, check the last messages on the container where kernel module
was loaded:
root@ec0adc35856f:/home/22kerny#dmesg | grep tail
[217095.561631] src mac 02:42:ac:11:65:43, dst mac 02:42:ac:11:00:02
[217095.561633] src IP addr: 172.17.0.3
Well, well... yo dah man!!!
But what if I add these small 3 lines in my code (look at the bold lines):
root@ec0adc35856f:/home/33kerny# more heh.c
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/netfilter.h>
#include<linux/netfilter_ipv4.h>
#include<linux/skbuff.h>
#include<linux/udp.h>
#include<linux/icmp.h>
#include<linux/ip.h>
#include<linux/inet.h>
#define device "eth0"
#define NEW_IDENTITY "122.122.122.122"
static struct nf_hook_ops nfin;
static unsigned int hook_func_in(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
struct ethhdr *eth;
struct iphdr *ip_header;
if (!device)
return NF_ACCEPT;
eth = (struct ethhdr*)skb_mac_header(skb);
ip_header = (struct iphdr *)skb_network_header(skb);
printk(KERN_INFO "src mac %pM, dst mac %pM\n", eth->h_source, eth->h_dest);
printk(KERN_INFO "src IP addr: %pI4\n", &ip_header->saddr);
ip_header->saddr=in_aton(NEW_IDENTITY);
printk(KERN_INFO "now modified src IP addr: %pI4\n", &ip_header->saddr);
return NF_ACCEPT;
}
/* On older vesion than 4.13 kernel (or newer version than 4.13)
netfilter function has the form:
unsigned int hook_func_in(unsigned int hooknum,
struct sk_buff **skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *)) */
static int __init init_main(void)
{
nfin.hook = hook_func_in;
nfin.hooknum = NF_INET_PRE_ROUTING;
nfin.pf = PF_INET;
nfin.priority = NF_IP_PRI_FIRST;
nf_register_net_hook(&init_net, &nfin);
//on older versions than 4.13 kernel
//or newer version than 4.13 kernel
//use: nf_register_hook(&nfin)
return 0;
}
static void __exit cleanup_main(void)
{
nf_unregister_net_hook(&init_net, &nfin);
//on older versions than 4.13 kernel
//or newer version than 4.13 kernel
//use: nf_unregister_hook(&nfin)
}
module_init(init_main);
module_exit(cleanup_main);
Our Makefile:
obj-m := heh.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
clean:
rm *o
Build it, load it:
root@ec0adc35856f:/home/33kerny# make
make -C /lib/modules/4.13.0-32-generic/build SUBDIRS=/home/33kerny modules
make[1]: Entering directory `/usr/src/linux-headers-4.13.0-32-generic'
CC [M] /home/33kerny/heh.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/33kerny/heh.mod.o
LD [M] /home/33kerny/heh.ko
make[1]: Leaving directory `/usr/src/linux-headers-4.13.0-32-generic'
root@ec0adc35856f:/home/33kerny#
root@ec0adc35856f:/home/33kerny#insmod heh.ko
Resend again the raw packet:
root@f8918abfeb55:/home/ipy# ./tiny_tcp 172.17.0.2 172.17.0.3
Now, go back to your container with kernel module and check last messages:
root@ec0adc35856f:/home/33kerny# dmesg | tail
[217758.318273] src mac 02:42:ac:11:65:43, dst mac 02:42:ac:11:00:02
[217758.318277] src IP addr: 172.17.0.3
[217758.318280] now modified src IP addr: 122.122.122.122
Ahh, I love danger!
Unload your kernel module:
root@ec0adc35856f:/home/33kerny# lsmod | grep heh
heh 16384 0
root@ec0adc35856f:/home/33kerny# rmmod heh
root@ec0adc35856f:/home/33kerny# lsmod | grep heh
root@ec0adc35856f:/home/33kerny#
root@ec0adc35856f:/home/33kerny# echo $?
0
Well, kidz! that`s a wrap for today.
You make sure you read all the links I gave you.
Next time, we will be doing a nice introduction in R programming, Jupyter, and
data analysis for network traffic.
See y`all soon!
===========================
To be read:
All links provided in this tutorial. (yes, click on those underlined words).
All links from older tutorials.
-------EOF---------
|