Content of the first part:Creating a lab, architecture Netfilter, char device, sysfs The content of the second part:2.1 - Introduction to the second part. We look at the network and protocols. Wireshark.2.2 - Firewall Tables. Transport Layer. TCP structures, UDP. Extend the firewall.2.3 - Expanding functionality. We process data in user space. libnetfilter_queue.
2.4 - Bonus. We study the real Buffer Overflow attack and prevent it with the help of our Firewall.
Firewall rules. Theory.
In this part, we will almost finish learning a base sufficient to implement a simple firewall, but before we do this (it is assumed that the reader has knowledge in networks or read part 2.1), it is necessary to consider how the firewall makes decisions.
')


Such a table of rules is loaded by the user (administrator) into the firewall memory. It is they who determine when receiving packages what to do with them - accept or reject.
Important! When a firewall receives a packet, it necessarily looks at its fields (what we did in lesson 2.1) and compares them with the rules from the table in the order (!), How these rules are written in it (from top to bottom!). In other words, there is a fundamental difference, which rule will be in the table above, and which one is lower.
Important! Any strange package should not get into the network or device protected by us. In addition, if we do not know whether it can be skipped or not, the answer is NO. We only allow packets that are allowed in the firewall rules.
Hence the principle of building and operating a firewall: only those packets that we allowed can get into the internal network (we have this host1).
Now for example. The table above defines five rules. Upon receipt of each packet, we must make sure that only if we have found a suitable rule for it, and it is spelled out in
Action -
accept , only then we skip it. If we did not find a suitable rule after checking each of them, then we throw out the package, regardless of its content. To do this, there is the last default rule, which determines - to throw out any package that does not fall under any of the rules. It must be at the very end (in fact, all the firewall add it automatically, even if it is not spelled out).
Now more about the
direction and
ack fields.
Direction - determines if the packet is in our network or out. For example, we may want to prohibit all smtp (mail) protocol packets in order to avoid information leakage via e-mail. Or vice versa - we will want to prohibit any incoming packet via telnet protocol to prohibit any attempt to connect to our network. We will consider in the practical part how in our case to determine the direction of the packet in the code.
The first two rules are called
spoof , and they perform trivial defense against trivial attack attempts. So,
spoof1 means “any incoming packet (
direction = in ) with the address of our network (10.0.1.1 = host1) for any port numbers, protocols, etc. - throw out. The logic of this rule is that the packet cannot come to our network on the firewall, and at the same time it indicates that it was sent from our own network (
src ip = 10.0.1.1 ). In other words, this means that someone has faked it and is trying to disguise itself as one of the users (in this case, host1) - we don’t want to miss this package.
Symmetric rule and
spoof2 - we do not want to release packets from the internal network if it says that it is originally from an IP that is different from the internal addresses (that is,
NOT 10.0.1.1). Most likely, this is also some kind of virus.
ACK is a flag (one bit) that is used to establish a connection when using the TCP protocol and further maintaining its “reliability”. Each TCP connection starts with a triple handshake (3 way handshake, there is no article in Russian, but there is an animation in English here:
https://en.wikipedia.org/wiki/Handshaking#TCP_three-way_handshakeIt is important for us to understand that with each new opening of a TCP session, only in the first packet ACK = 0, in all other packets of the created session - ACK> 0 (
https://ru.wikipedia.org/wiki/TCP ).
Thanks to this fact, we can distinguish an already existing compound from an attempt to open it. If ACK = 0, then this is an attempt to create a TCP connection (the first packet in the triple handshake), if ACK = 1, then the connection should have been created before (and if this is not the case, then it is logical not to allow such packets to go to the network).
Now look at the
http_in ,
http_out rule :
http_in means the following: if the packet is incoming (
direction =
in ), from any IP (
Src IP = any , note that the spoof rules are higher, at this stage, guarantee us that this is not the IP of the internal network) intended for our network (
Dest IP == host1 == 10.0.1.1 ), sent via TCP, to port 80 (that is, the server known to all http), from any port (> 1023 means any non-reserved port that we receive from the operating system when creating a connection and in the future it is used to identify this particular compound, as described in part 2.1),
Ack = Any, this means that we we decide to ask the computer from the outside to open the connection (in the first packet, ack = 0, and the following ack> 0). And we accept such packets and skip further to the network (
action = accept ).
http_out is symmetrical, with the difference that we will not miss packets with ack = 0, only ack> 0, that is, we will not allow to create an http connection from our computer on the internet, but at the same time we will be able to respond to the connection already created in the http .
In other words, the http rules allow access to our network via http from the outside, but prohibit users of our network from using http (that is, access to internet sites).
Firewall rules. Practice.
Returning to our module, I remind you that the interception function looks like this:
unsigned int hook_func_forward(unsigned int hooknum, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *));
Let's look at the parameters:
hooknum - interception number, we have already passed it
const struct net_device * in, out - pointers to network interface structures
struct sk_buff * skb is the most interesting for us - a pointer containing the data we need
SKB - socket buffer , this is the basic structure of a linux network. It has many fields and can be a separate subject for writing an article. I found a couple of good links for those who want to go deep:
http://vger.kernel.org/~davem/skb.htmlhttps://people.cs.clemson.edu/~westall/853/notes/skbuff.pdfWe are interested:
union { struct tcphdr *th; struct udphdr *uh; struct icmphdr *icmph; struct igmphdr *igmph; struct iphdr *ipiph; struct ipv6hdr *ipv6h; unsigned char *raw; } h;
In the following way
struct iphdr *ip_header = (struct iphdr *)skb_network_header(skb);
we get a pointer to an
IP header (in part 2.1 we said that the main information at this level for us is
IP source ,
IP destination ).
Definition of
skb_network_header from inclue / linux / skbuff.h
http://lxr.free-electrons.com/source/include/linux/skbuff.h?v=3.0#L1282That is, we see that the function returns the necessary pointer from the “necessary” place in the
struct skbuff structure.
And now, when we have access to the
IP header , we can get IP addresses:
unsigned int src_ip = (unsigned int)ip_header->saddr; unsigned int dest_ip = (unsigned int)ip_header->daddr;
As well as the protocol number:
ip_header-> protocol
Access to Transport Layer (TCP / UDP ..)
struct udphdr *udp_header = (struct udphdr *)(skb_transport_header(skb)+20); struct tcphdr *tcp_header = (struct tcphdr *)(skb_transport_header(skb)+20);
For TCP (and similarly for UDP) port numbers:
unsigned int src_port = (unsigned int)ntohs(tcp_header->source); unsigned int dest_port = (unsigned int)ntohs(tcp_header->dest);
Below, I will provide the complete function code. An interesting point is the use of the function
ntohs .
ntohs is a function that changes the order of bits (the representation of a number). There are two types of memory representation used —
little endian and
big endian . The
big endian system is used to represent numbers on the network, while the
little endian architecture
(byte order)Therefore, to obtain the correct numbers, you must use these translating functions.
Below is the text of the entire function, which, when receiving a packet, prints all the necessary data for deciding on the firewall rules:
unsigned int hook_func_forward(unsigned int hooknum, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { struct iphdr *ip_header = (struct iphdr *)skb_network_header(skb); struct udphdr *udp_header = NULL; struct tcphdr *tcp_header = NULL; unsigned int src_ip = (unsigned int)ip_header->saddr; unsigned int dest_ip = (unsigned int)ip_header->daddr; unsigned int src_port = 0; unsigned int dest_port = 0; char src_ip_str[16], dest_ip_str[16]; if(ip_header->protocol == PROT_UDP) { udp_header = (struct udphdr *)(skb_transport_header(skb)+20); src_port = (unsigned int)ntohs(udp_header->source); dest_port = (unsigned int)ntohs(udp_header->dest); } else if(ip_header->protocol == PROT_TCP) { tcp_header = (struct tcphdr *)(skb_transport_header(skb)+20); src_port = (unsigned int)ntohs(tcp_header->source); dest_port = (unsigned int)ntohs(tcp_header->dest);
Compile
Most (if not all) examples of writing modules or using
netfilter are limited to one source file and a couple dozen lines of code. But large projects cannot (and incorrectly) fit in one source file; and although the example I describe can be crammed into one file, I decided to divide it into module_fw.c — all that concerns char device, sysfs, kernel module, and hook_functions.c — interception functionality. When compiling a kernel module consisting of several files, there is a small trick to be aware of, below is an example:

Here you should pay attention to the line:
obj-m: = fw.oThere is no such file fw.c, so this is the name of the module that will be created. Also, this is a prefix for the next line, which describes all the files related to the module.
fw-objs + =You need to know that, of course, the name of the module and the source code should not be the same. Otherwise, everything remains the same.
Check
For verification, I quickly configured the
dhcp interfaces (see part 1) and put it on host1,
http server — apache2 , and on host2 —
lynx — a text browser (although telnet could have been done). Run
lynx 10.0.1.1

We look that gives our firewall:

Well, that's all.
Conclusion
In this part, we looked at how the table of rules in the firewall, which determine the policy of protecting and passing traffic to the network. After that, we disassembled one of the basic network structures of
skbuf in Linux and, thanks to this, were able to supplement our program with all the necessary information to supplement the support for the tables in our module. What is left is to write the load of this table via sysfs, as we did in part 1, and add
if {} else if {} else {} ... to the
hook_func_forward function. I’ll leave it to those who want it, as there’s nothing fundamentally new here ... well, it can only work with the
klist , but this is a completely different topic, which is also well covered on the Internet.
In the function itself, you can find a bonus, designated as XMAS packet, and read what it is and why on the Internet, and we will begin the next part of this (is this “here”?) -
if(dest_port == HTTP_PORT || src_port == HTTP_PORT){ printk("HTTP packet\n"); }
References:wikipedia.org/wiki/Handshaking#TCP_three-way_handshakeru.wikipedia.org/wiki/TCPvger.kernel.org/~davem/skb.htmlpeople.cs.clemson.edu/~westall/853/notes/skbuff.pdflxr.free-electrons.com/source/include/linux/skbuff.h?v=3.0#L1282Byte order