📜 ⬆️ ⬇️

Creating and testing a firewall in Linux, Part 1.3. Writing char device. Adding a virtual file system ...

Content of the first part:

1.1 - Creating a virtual lab (so that we have where to work, I'll show you how to create a virtual network on your computer. The network will consist of 3 Linux ubuntu machines).
1.2 - Writing a simple module in Linux. Introducing Netfilter and intercepting traffic with it. We combine everything together, we test.
1.3 - Writing a simple char device. Adding a virtual file system - sysfs. Writing user interface. We combine everything together, we test.

The content of the second part:
')
Hidden text
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 the data in user space. libnetfilter_queue.
2.4 - (* Optionally) We study the real Buffer Overflow attack and prevent it with the help of our Firewall.

Part 1.3

In the previous parts, we have prepared a module (kernel space), which can already do a minimal job - give some packages pass, and some do not. Now it was not bad to add the ability to receive data and control the operation of the module from the usual program (user space). For example, enable Firewall, turn off and get job statistics. There are several ways to do this. We will go in the classic way.

Introduction to Character device. Theory


I advise to read the English version of the Wikipedia article , as more accurate and voluminous on this topic than the Russian. It will be enough for us to understand that different hardware devices (hardware) work and interact with the operating system at a very low level and of course all this happens in the kernel space. Linux / Unix systems, created mechanisms, one of which is a character device , in order to simplify interaction with these devices. When creating a character device , we “ask” the OS for the necessary resources and can present the device as a file in a special directory (as is known in linux, everything is represented as files and read / write operations into it). When reading from this file, we can receive data from the device (for example, packets that came to the network card), and when writing to this file, we can send data to the device (for example, send a document to the printer for printing). In our case, the device is not physically present, but we will use these files to interact with our program.

Introduction to Character device. Practice


It is very simple to create and register such a driver; just call the function.
fw_major = register_chrdev(0, DEVICE_NAME, &fops); . After this function, you can go to / dev / manually add a device and start working with it. The fops structure defines the functions that will be called on various events with the driver, for example, reading or writing. The function returns a major number - a unique identification number.

In this case, I chose only two events, but the links that I give below, or by reading the kernel source itself, you can find a complete list (quite large).

Below, I determined that when reading from our device, we get the number of all captured packets, and when writing, we reset them:

 static ssize_t fw_device_read(struct file* filp, char __user *buffer, size_t length, loff_t* offset) { printk("Reading from device, return total number of messages\n"); return sprintf(buffer, "%u", accepted_num + dropped_num); } static ssize_t fw_device_write(struct file *fp, const char *buff, size_t length, loff_t *ppos) { printk("Writing to device, clear number of messages\n"); accepted_num = dropped_num = 0; return 0; } static struct file_operations fops = { .read = fw_device_read, .write = fw_device_write }; 

In order not to add a device manually each time, you can make its registration automatic:

 fw_class = class_create(THIS_MODULE, DEVICE_NAME); fw_device = device_create(fw_class, NULL, MKDEV(fw_major, 0), NULL, DEVICE_FW); 

A small check:



You can already see the device in / dev, and after registering the class, it also appears in / sys / class

Below is a full listing, note the use of goto. Usually (= always), we do not use goto in programming, because it spoils the comprehension, content, readability of the code and most likely speaks of problems in the design of the program ( spaghetti code ). But this case is one of the few where goto is very relevant.

 static int fw_major; static struct device* fw_device; static ssize_t fw_device_read(struct file* filp, char __user *buffer, size_t length, loff_t* offset) { printk("Reading from device, return total number of messages\n"); return sprintf(buffer, "%u", accepted_num + dropped_num); } static ssize_t fw_device_write(struct file *fp, const char *buff, size_t length, loff_t *ppos) { printk("Writing to device, clear number of messages\n"); accepted_num = dropped_num = 0; return 0; } static struct file_operations fops = { .read = fw_device_read, .write = fw_device_write }; static int __init fw_module_init(void) { int retval = 0; printk("Starting FW module loading\n"); … accepted_num = 0; dropped_num = 0; // device functions fw_major = register_chrdev(0, DEVICE_NAME, &fops); if (fw_major < 0) { printk("failed to register device: error %d\n", fw_major); goto failed_chrdevreg; } fw_class = class_create(THIS_MODULE, DEVICE_NAME); if (fw_class == NULL) { printk("failed to register device class '%s'\n", DEVICE_NAME); goto failed_classreg; } fw_device = device_create(fw_class, NULL, MKDEV(fw_major, 0), NULL, DEVICE_FW); if (fw_device == NULL) { printk("failed to create device '%s_%s'\n", DEVICE_NAME, DEVICE_FW); goto failed_devreg; } // netfilter functions … return 0; failed_devreg: class_destroy(fw_class); // unregister the device class failed_classreg: unregister_chrdev(fw_major , DEVICE_NAME); // remove the device class failed_classreg: failed_chrdevreg: // unregister the major number failed_chrdevreg: return -1; } 

User interface


Now we will write a simple program that will read and write to the created device to test the work:

 // test.c #include <stdio.h> #include <string.h> #include <fcntl.h> int reset() { char path[] = "/dev/device_fw"; int fd = open(path, O_WRONLY); if (fd<0) { printf("Device access error, fd = %d\n", fd); return fd; } char msg = '1'; write(fd, &msg, sizeof(msg)); close(fd); return 0; } int all_msg() { char msg[1] = {0}; int fd = open("/dev/device_fw", O_RDONLY); if (fd<0) { printf("Device access error, fd = %d\n", fd); return fd; } if(read(fd, &msg, 1)>0){ printf("Accepted packets number: %s\n", msg); } else { printf("Nothing to read\n"); } close(fd); return 0; } int main(int argc, char* argv[]) { if(argc <= 1 || argc > 3) { printf("Wrong number arguments. Number of arguments is %d\n", argc); return -1; } if(strcmp(argv[1], "reset")==0){ reset(); } else if(strcmp(argv[1], "all_msg")==0){ all_msg(); } else { printf("Wrong argument %s\n", argv[1]); } return 0; } 

And check the work:



As you can see above, we first loaded our module. Then they compiled a program to read / write to the device. The first time we ran sudo ./test all_msg , we read from the device and got the number 0. After that, we sent a 4 ping request to one of the network devices. Again, we read, received 16 packets (why not 8m?;). We executed sudo ./test reset , which addressed the device to the recording, which in turn reset everything.

So it looks from the point of view of the driver:



Before we continue, I strongly advise (but not necessarily) to read here for deepening. And also here . Below there is a link to a good free book.

Introduction to sysfs. Theory


We could continue the driver-user communication through reading / writing to / dev / fw_device , but this is not recommended if you need to send / receive a lot of information (unlike the bytes in our example), and this method is also considered obsolete. And although there are no large volumes in this article, I will show how to use sysfs, for communication kernel <-> user .

sysfs is a virtual file system in the Linux operating system. Exports Linux kernel information about devices and drivers present on the system into user space. First appeared in the kernel version 2.6. The need to create was caused by the outdated system of the kernel with devices. ( Https://ru.wikipedia.org/wiki/Sysfs )

That is, thanks to sysfs, we can create entire structures with a hierarchy of files that will be displayed in / sys / class / fw and use them for reading or writing. For example, we will create two files:

/ sys / class / fw / acceptedMessages - reading from which will return the number of received packets
/ sys / class / fw / dropedMessages - reading from which will return the number of prohibited packets

This is done very simply. Please note that after calling above:

 fw_class = class_create(THIS_MODULE, DEVICE_NAME); 

We have already registered the class and have already seen it in / sys / class. It remains to add two files and define their functions. Register files:

 static int __init fw_module_init(void) { … fw_device = device_create(fw_class, NULL, MKDEV(fw_major, 0), NULL, DEVICE_FW); … retval = device_create_file(fw_device, &dev_attr_acceptedMessages); if (retval < 0) { printk("failed to create acceptedMessages /sys endpoint - continuing without\n"); } retval = device_create_file(fw_device, &dev_attr_droppedMessages); if (retval < 0) { printk("failed to create droppedMessages /sys endpoint - continuing without\n"); } … } 

At the beginning of the module, add the DEVICE_ATTR macros that define read or write, as well as the functions that will be called. Since we don’t need to process the record, the last field is NULL.

 static DEVICE_ATTR(acceptedMessages, S_IWOTH | S_IROTH, sys_read_accepted_msg, NULL); static DEVICE_ATTR(droppedMessages, S_IWOTH | S_IROTH, sys_read_dropped_msg, NULL); 

And the functions themselves:

 static ssize_t sys_read_accepted_msg(struct device *dev, struct device_attribute *attr, char *buffer) { return sprintf(buffer, "%u", accepted_num); } static ssize_t sys_read_dropped_msg(struct device *dev, struct device_attribute *attr, char *buffer){ return sprintf(buffer, "%u", dropped_num); } 

Accessing them through our user interface is exactly the same as with / dev /. For example:

 int dropped_num() { char msg[255] = {0}; int fd = open("/sys/class/fw/device_fw/droppedMessages", O_RDONLY); if (fd<0) { printf("Device access error, fd = %d\n", fd); return fd; } if(read(fd, &msg, 255)>0){ printf("Accepted packets number: %s\n", msg); } else { printf("Nothing to read\n"); } close(fd); return 0; } 

Now is the time to put everything together, compile and thoroughly check.
Sysfs:



Accepted packets: do ping



And in parallel we read



Check for dropped packets :
Trying to ping from host2 to host1



In parallel, we look at the "logs"



By the way, note that here, the counter is constantly increasing by one (and not by two, as before), because host1 does not receive requests from host2 and accordingly does not respond. And for the interest of dmesg :





Lastly, I will upload fw and check that the network works without it without restrictions:





We see that without our module, ping passes without problems.

Conclusion


In the first part, we first created a virtual network to work with three computers. Then we looked at writing a simple module that netfilter used to intercept traffic. And at the end, we added a char device and sysfs, to present the functions of the module in the file system to the average user through reading / writing to files. At the end we wrote a program for the user to control our device.

I would be very happy with any constructive comments. In the next part, we will significantly expand the functionality of this module, make it more similar to a simple firewall, and also see how it can protect the network from various types of attacks.

Links

Linux Device Drivers, Third Edition
» Http://derekmolloy.ie/writing-a-linux-kernel-module-part-2-a-character-device/
» Http://pete.akeo.ie/2011/08/writing-linux-device-driver-for-kernels.html
» Https://ru.wikipedia.org/wiki/Sysfs
» Https://en.wikipedia.org/wiki/Device_file#Character_devices
» Spaghetti code

Source: https://habr.com/ru/post/315454/


All Articles