Interception of function calls in Linux or the simplest firewall do it yourself
Introduction
Impatient, these lyrical digressions can not be read.
Some time ago, I was visited by the thought: “How to make it so that any one program on Linux does not access the Internet (or any particular host)?” This thought visited me and flew further on my own business. And today I received one question in my RSS reader on askdev.ru . Bah! Yes, this is exactly what I was thinking! It is necessary to help a person, at the same time and to understand the issue himself.
I climbed into Google to see if there were any hints of a solution. From there I learned that for some time now it has become impossible to do regular iptables , and people recommend looking in the direction of AppArmor . “Burning with a great desire to learn AppArmor", I began to search further and almost accidentally stumbled upon a message on the ENT specialist , which described a rather interesting method.
Method
It consisted of “replacing” the connection function with one of its own, which decides whether to allow the connection and “skip” the request for the real function or not — return an error. In the message of the esteemed Chaoser, a low-level blocking of sockets was used, which did not make it possible to make a decision depending on the server address and / or port. This did not suit me, I needed to deny access for only one port - the 80th. Running telnet , under strace , I almost immediately saw a suitable victim - the connect function . strace described it like this:
connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("87.250.251.3")}, 16) = 0
In this description, it is clearly visible that there are all the components I need: the IP address, port, and connection type (AF_INET).
Well, let's get started.
Decision
To implement this method, we will write a library that will load earlier than others when the application starts, and thus intercept the functions that are defined in it (and which are designed for other libraries).
To start, I will lay out all the code, and then we will analyze everything separately.
- #define _GNU_SOURCE
- #include <dlfcn.h>
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <errno.h>
-
- static int (*real_connect)(int sockfd, const struct sockaddr *addr,
- socklen_t addrlen) = 0;
-
- int connect(int sockfd, const struct sockaddr *addr,
- socklen_t addrlen)
- {
- printf("NF_DEBUG: -----------------------------------------------\n");
- int sa_family = addr->sa_family;
- printf("NF_DEBUG: Address family: %d (AF_INET = %d)\n", sa_family, AF_INET);
-
- if (sa_family == AF_INET)
- {
- struct sockaddr_in *addr_in = (struct sockaddr_in*)(addr);
-
- struct in_addr sin_addr = addr_in->sin_addr;
- uint16_t sin_port = addr_in->sin_port;
- uint16_t sin_port_h = ntohs(sin_port);
-
- printf("NF_DEBUG: IP: %s\n", inet_ntoa(sin_addr));
- printf("NF_DEBUG: Port: %d\n", sin_port_h);
-
- if (sin_port_h == 80)
- {
- printf("NF_DEBUG: Rejected!\n");
- printf("NF_DEBUG: -----------------------------------------------\n");
- errno = ENETUNREACH;
- return -1;
- }
- }
-
-
- if(!real_connect)
- real_connect = dlsym(RTLD_NEXT, "connect");
-
- printf("NF_DEBUG: Accepted\n");
- printf("NF_DEBUG: -----------------------------------------------\n");
- return real_connect(sockfd, addr, addrlen);
- }
* This source code was highlighted with Source Code Highlighter.
Code parsing
Those who understand how the above program works can safely skip this section. It will describe which line does what. This should be useful for beginners. Those who know C well may only be interested in a few points from this.
Lines 1-8 are preprocessor directives; there is nothing interesting in them. Unless the directive #define _GNU_SOURCE , which connects the GNU extensions necessary for the dlsym function .
In lines 10–11, we declare a pointer to the “real” connect function. With us it will be called real_connect . The description of the function is taken from man connect .
Line 13 begins the new connect functionwhich applications will call and which will decide whether to skip this application or not. Its description is fully consistent with the original connect and is taken from the same manual.
In the 17th line from the structure that contains all the data we need (and the description of which I took here ) we get the address type.
If the address type is AF_INET (line 20), that is, the application is “asking outside” via IPv4, then we use filtering (lines 22-37), otherwise, we immediately skip this connection to the “real” connect function .
In order to filter connections, you need to convert the addr structure to sockaddr_in type, which allows access to the required fields. This happens on line 22.
Next, on lines 24-25, we get the address and port values, respectively. On line 26, we change the order of the bytes in the port number, thereby obtaining the usual format of the port number (80, 110, 25) from the format that is used during the connection.
In line 28, the resulting address is reduced to a text form and displayed, in the 29th line we display the port number.
Next, in line 31, we check whether the port number corresponds to the one we want to block, if so, we report it (lines 33-34), set the error number (line 35; in this case there will be a “Network is unavailable” error) and return the error (line 36).
If everything is in order, that is, the application accessed using either the allowed port or the allowed type of address, then we get the address (entry point) of the “real” connect function (line 42), if we have not received it before (line 41; not we forget that real_connect is a global variable), write that everything is fine (lines 44-45), and transfer control to the “real” connect function .
Upon receiving the address of the “real” connect function , we use the RTLD_NEXT parameter in order to get this address from the next loaded library, and not from the first (ours), otherwise we will get infinite recursion.
Using
Compile the library:
gcc -fPIC -shared -Wl,-soname,nonet.so -o nonet.so nonet.c
and run the desired application as follows:
LD_PRELOAD=/<полный путь к полученной библиотеке>/nonet.so <приложение>
For example, if the library is in / tmp , then the next line you can start firefox without access to the Internet (of course, only through the 80th port):
LD_PRELOAD=/tmp/nonet.so firefox
Conclusion
This method in no way claims to universality and universality, as well as an article for the Nobel Prize. Here I just covered two topics that are interesting to me, and, I hope, will be interesting to someone else: restricting network access to certain programs and intercepting function calls in Linux.
Thanks for attention!
PS So that I would not be considered a dishonest person who stole the code from digital from askdev.ru, I think it should be mentioned that digital and I are the same person.
UPD Thank you very much for inviting habercheloveka peter23 . Moved a blog post to Linux for All.