Creating and testing Firewall on Linux, Part 2.2. Firewall Tables. Access to TCP \ IP Structures

Published on January 05, 2017

Creating and testing Firewall on Linux, Part 2.2. Firewall Tables. Access to TCP \ IP Structures

    The content of the first part:


    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 Structures TCP, UDP. Extend Firewall.
    2.3 - Extend functionality. We process data in user space. libnetfilter_queue.
    2.4 - Bonus. We study the real Buffer Overflow attack and prevent it with our Firewall.

    Firewall rules. Theory.


    In this part, we will almost finish studying the base sufficient to implement a simple firewall, but before we do this (it is assumed that the reader has knowledge of the networks or has read part 2.1), it is necessary to consider how the firewall makes decisions.





    Such a rule table is loaded by the user (administrator) into the firewall memory. It is they who determine when receiving packets what to do with them - accept or reject.

    Important! When the firewall receives the packet, it will necessarily look at its fields (what we did in lesson 2.1) and compare them with the rules from the table in the order (!) As 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 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, then 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 packets that we allowed can (and should) get into the internal network (we have it host1).

    Now for example. The table above defines five rules. Upon receipt of each package, we must make sure that only if we find a suitable rule for it, and it is written 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 that defines - throw out any package that does not fall under any of the rules. It must be at the very end (in fact, all firewalls add it automatically, even if it is not registered).

    Now more about the direction and ack fields .

    Direction- determines whether a packet enters our network or exits. For example, we might want to block 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 using the 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 provide trivial protection 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 it away. ” The logic of this rule is that a packet cannot come to our network on the firewall, and it indicates that it was sent from our network ( src ip = 10.0.1.1 ). In other words, this means that someone forged it and is trying to disguise itself as one of the users (in this case, host1) - we do not want to skip such a package.

    Symmetric rule and spoof2 - we don’t want to release packets from the internal network if it says that it is originally with 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- This is a flag (one bit) that is used to establish a connection when using the TCP protocol and to further maintain its “reliability”. Each TCP connection begins with a triple handshake (3 way handshake, there is no article in Russian, but there is animation in English here: https://en.wikipedia.org/wiki/Handshaking#TCP_three-way_handshake

    It’s important for us to understand that with every new opening TCP sessions, only in the first packet ACK = 0, in all other packets of the created session ACK> 0 ( https://ru.wikipedia.org/wiki/TCP ).

    Due to this fact, we can distinguish an existing connection from an attempt to open it. If ACK = 0, then this is an attempt to create a TCP connection (the first packet in a triple handshake), if ACK = 1, then the connection should have been created before (and if this is not so, then it would be logical to prevent such packets from reaching the network).

    Now let's look at the http_in rule , http_out :



    http_in means the following: if the packet is incoming ( direction = in ), from any IP ( Src IP = any , note that the spoof rules above, at this stage, guarantee us that this is not an internal IP network), designed for our network ( Dest IP == host1 == 10.0.1.1), sent over TCP, to port 80 (that is, the http server known to everyone), from any port (> 1023 denotes any unreserved port that we receive from the operating system when creating a connection and is used in the future to identify this particular connection, as described in part 2.1), Ack = Any, this means that we allow the computer to ask from the outside to open the connection (in the first packet ack = 0, and the next ack> 0). And we accept such packets and pass them 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 an http connection from our computer to be created on the internet, but we can respond to an already created http connection .

    In other words, 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 is the interception number, we have already passed
    const struct net_device * in, out are pointers to the network interface structure
    struct sk_buff * skb - the most interesting thing for us is the pointer containing the
    SKB data we need - socket buffer , this is the base linux network structure. It has many fields and can be a separate subject for writing an article. I found a couple of good links for those wishing to delve deeper:

    http://vger.kernel.org/~davem/skb.html
    https://people.cs.clemson.edu/~westall/853/notes/skbuff.pdf

    We are interested in:

    union {
    		struct tcphdr	*th;
    		struct udphdr	*uh;
    		struct icmphdr	*icmph;
    		struct igmphdr	*igmph;
    		struct iphdr	*ipiph;
    		struct ipv6hdr	*ipv6h;
    		unsigned char	*raw;
    } h; // Transport header
    union {
    		struct iphdr	*iph;
    		struct ipv6hdr	*ipv6h;
    		struct arphdr	*arph;
    		unsigned char	*raw;
    } nh; // Network header

    In the following way

    struct iphdr *ip_header = (struct iphdr *)skb_network_header(skb);

    we get a pointer to the IP header (in part 2.1 we said that the main information at this level for us is IP source , IP destination ).

    The definition of skb_network_header from inclue / linux / skbuff.h



    http://lxr.free-electrons.com/source/include/linux/skbuff.h?v=3.0#L1282

    That is, we see that the function returns the desired pointer from the "necessary »Places in the struct skbuff structure .

    And now that we have access to the IP header , we can get the IP addresses:

    unsigned int src_ip = (unsigned int)ip_header->saddr;
    unsigned int dest_ip = (unsigned int)ip_header->daddr;

    And also 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 give the full function code. An interesting point would be to use the ntohs function . ntohs is a function that changes the order of bits (representing numbers). There are two types of representation of a number in memory used - little endian and big endian . To represent numbers on the network, the big endian system is used , while the Intel little endian architecture (Byte order)
    Therefore, to obtain the correct numbers, you must use these translation functions.
    Below is the text of the entire function, which upon receipt of the packet prints all the necessary data to make a decision according to 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);
    // XMAS packet
    // FIN, URG, PSH set
    //	if(ip_header->protocol == PROT_TCP){
    //		printk("TCP ack = %s\n", tcp_header->ack == 1 ? "yes" : "no");	
    // if (tcp_header->fin > 0 && tcp_header->urg > 0 && tcp_header->psh > 0 ){
    //			info("XMAS packet detected, drop");
    //	}
    	}
    	ip_hl_to_str(ntohl(src_ip), src_ip_str);
    	ip_hl_to_str(ntohl(dest_ip), dest_ip_str);
    	printk("---------------------------\n");
    	printk("in device = [%s], out_device = [%s]\n", in->name, out->name);
    	printk("ip_src = [%s], ip_dest = [%s]\n", src_ip_str, dest_ip_str);
    	printk("src port: [%u], dest port: %u, \n", src_port, dest_port);           
    	printk("protocol = %d\n", ip_header->protocol);
    	if(dest_port == HTTP_PORT || src_port == HTTP_PORT){
    		printk("HTTP packet\n");
    	}
    	return NF_ACCEPT;
    }

    Compiling


    Most (if not all) examples of writing modules or using netfilter are limited to one source file and a couple of dozen lines of code. But large projects cannot (and incorrectly) fit into a single source file; and although the example I am describing can be crammed into a single file, I decided to split it into module_fw.c - everything about 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 that you need to know, below is an example:



    Here you should pay attention to the line:

    obj-m: = fw.o
    there is no such file fw.c, therefore this is the name of the module that will be created. Also, this is the 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 must not match. Otherwise, everything remains the same.

    Check


    For verification, I quickly configured dhcp interfaces (see part 1) and installed apache2 on host1, http server , and lynx on text2 on host2 (although telnet could be dispensed with). We start

    lynx 10.0.1.1



    We look that our firewall gives out:



    Well, that's all.

    Conclusion


    In this part, we examined how the rules tables in the firewall work, which determine the policy for protecting and passing traffic on 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 table support in our module. What remains is to write the download 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 everyone, since there’s nothing fundamentally new here ... well, maybe only working with klist , but this is a completely different topic, which is also well covered on the Internet.

    In the function itself, you can find the bonus, designated as XMAS packet, and read what it is and why on the Internet, and the next part we will start the two (is it "here"?) -

    if(dest_port == HTTP_PORT || src_port == HTTP_PORT){
    	printk("HTTP packet\n");
    }

    Links:
    wikipedia.org/wiki/Handshaking#TCP_three-way_handshake
    en.wikipedia.org/wiki/TCP
    vger.kernel.org/~davem/skb.html
    people.cs.clemson.edu/~westall/853/notes/skbuff .pdf
    lxr.free-electrons.com/source/include/linux/skbuff.h?v=3.0#L1282 Byte
    order