Linux has a wide variety of tools that allow you to fully control what’s happening. One of them is LD_PRELOAD, which is an environmental variable that allows you to load any library of your choice before anything else. There are a number of LD_PRELOAD tricks that you can use to control and modify software within your environment.
At Apriorit, we specialize in cybersecurity and virtualization and often use hooks for monitoring and system management. We had one case, where we tried to install hooks using the method with the constructor attribute. But when adding hooks for the read method, we encountered a problem where the read method was called earlier than the method with the constructor attribute. As a result, the hooks weren’t installed and our application crashed.
This LD_PRELOAD example was born from our search for a solution to this problem. When searching for the solution, we conducted detailed research of the constructor attribute and how to use it. Below you will find our results.
The GCC website provides a detailed description of the constructor attribute. The gist is that the constructor attribute works similarly to the destructor attribute, only they do opposite things. The constructor makes it so that a function is called automatically while the execution enters main(). The destructor makes it so that a function is called when exit() is called or when main() has finished. Both of these functions are useful for initializing data that will be used by your program.
To control the order in which constructors and destructors run, you need to provide an integer to define the priority. A destructor with a higher priority number will run before a destructor with a lower number. The opposite is true for constructors – a constructor with a lower number will run earlier.
If you need both a constructor and destructor to handle the same resource, you would usually assign them the same priority. The properties of destructors and constructors are similar to those specified for namespace-scope C++ objects.
The constructor attribute guarantees that all methods with this attribute will be called before main() but does not guarantee that the method with the attribute will be called before other methods.
Here’s a short example that illustrates the behavior of the constructor attribute:
Here’s the result that you get after launching the application linked with the library from the example above:
In this case, all constructors are called sequentially. When init_method2 is called by the read method (and since the init_method constructor hasn’t been called yet), orig_read is not initialized, and thus you’ll get this message:
In this situation, the problem of launch order can be solved by setting constructor priorities:
First, the constructor with the lowest priority number will be called. In this case, it’s init_method. You can use numbers higher than 100 to set priorities. Constructor priorities from 0 to 100 are reserved for the implementation.
The example described above is far removed from real cases that we encounter in practice, since we can clearly see all dependencies. Let’s take a look at a more realistic case where several libraries are interacting. For this, we’ll leave only one method with the constructor attribute in the first library.
We’ll also add a hook for write to this library that will use the test_func method from another library.
Here’s the code of the library that defines test_func:
In this library there’s a method with the constructor attribute, with the init_test_lib inside the read_first_byte method using the read call. When running the test app linked to these libraries, you’ll get the following result:
In order to fully understand what’s going on, you can use LD_DEBUG=all and check what the loader does:
Initialization occurs in the following sequence:
1. libtest_lib.so is initialized (this is the library that defines the test_func method)
2. Symbols are searched for puts and _dl_find_dso_for_object
3. The init_test_lib constructor is called
4. Symbols are searched for read_first_byte
5. Symbols are searched for reads and puts
In this case, read is attached to the uninitialized library libconstructor.so
6. The read method from libconstructor.so is called
7. The /home/user/constructor/bin/Debug/libconstructor.so initialization is called
8. The init_method constructor is called
Thus, we can conclude that if you plan on reloading certain methods in the dynamic library you shouldn’t do it in a method with the constructor attribute. You can transfer the load right into the reloaded method. For example, for read you can do the following:
We hope that this article has cleared up some of your questions on how to use LD_PRELOAD with the constructor attribute. This is a powerful tool, and a single LD_PRELOAD exploit can be used to gain full control over an application, so make sure to use it responsibly. If you ever need an experienced development team with great knowledge of Linux and low-level development, you can always send us your request for proposal