Monitoring Dynamic Linker Hijacking With eBPF
The article showcases using eBPF for detecting Dynamic Linker Hijacking in Linux by monitoring changes to the /etc/ld.so.preload file.
Join the DZone community and get the full member experience.
Join For FreeExtended Berkeley Packet Filter (eBPF) is a programming technology designed for the Linux operating system (OS) kernel space, enabling developers to create efficient, secure, and non-intrusive programs. Unlike its predecessor, the Berkeley Packet Filter (BPF), eBPF allows the execution of sandboxed programs in privileged contexts, such as the OS kernel, without the need to modify kernel source code or disrupt overall program execution. This technology expands the features of existing software at runtime, facilitating tasks like packet filtering, high-performance analyses, and the implementation of firewalls and debugging protocols in both on-site data centers and cloud-native environments.
While Dynamic Linker Hijacking is frequently utilized by malware to establish persistence on a system, eBPF can effectively monitor attempts of Dynamic Linker Hijacking, with a specific emphasis on modifications to the /etc/ld.so.preload file. We'll showcase the usage of eBPF to intercept relevant syscalls and explain how preloaded libraries are typically used by malware to inject arbitrary code into the execution flow of trusted programs.
Note: This article is primarily created to showcase eBPF usage and its capabilities. The provided solution is not intended as a serious security solution but rather as an educational example. Please also note that there are easier ways to track file modifications — e.g., you can simply use tail or inotifywait. It’s also pretty untypical to have /etc/ld.so.preload in a traditional Linux setting, so just checking that the file exists already should fire a couple of red flags.
Dynamic Linker Hijacking Intro
Dynamic Linker Hijacking is a technique wherein attackers exploit the dynamic linking process to inject malicious code into trusted programs. In Linux, programs rely on shared libraries (shared objects or SO files) for various functionalities. Dynamic linking allows efficient code reuse and resource sharing.
Attackers place a malicious shared library in a location where the target program searches for libraries. When the program runs and dynamically links to this shared library, the attacker's code is loaded into the program's memory.
Notably, attackers commonly target /etc/ld.so.preload and environment variables like LD_PRELOAD to achieve Dynamic Linker Hijacking.
/etc/ld.so.preload is a configuration file on Linux that contains a list of paths to additional shared libraries that should be loaded before all other libraries when a program starts. When a program is executed, the dynamic linker/loader (ld.so) resolves and loads the required shared libraries. By default, the linker looks for libraries in standard paths such as /lib and /usr/lib. However, the presence of the /etc/ld.so.preload file allows users to extend this behavior.
In the context of Dynamic Linker Hijacking, attackers may manipulate /etc/ld.so.preload to preload a malicious shared library before the legitimate libraries. This way, when a trusted program starts, the attacker's code is loaded into the program's memory space, enabling unauthorized access or other malicious activities.
It's important to note that modifications to /etc/ld.so.preload typically require superuser (root) privileges.
Learn more about Linux attack techniques and overwriting preload libraries.
eBPF Intro
eBPF, or extended Berkeley Packet Filter, originated as an enhancement to the traditional BPF in the Linux kernel. Initially designed for network packet filtering, eBPF has evolved into a versatile technology with broader applications. Look at some examples on Wikipedia.
Nowadays, it’s really available to everyone and fairly easy to develop and debug. This is partially thanks to bcc — a set of tools that makes BPF programs easier to write, with kernel instrumentation in C (and includes a C wrapper around LLVM), and front-ends in Python and Lua.
You can read more about eBPF here and here, or immerse yourself in this beautiful book.
The eBPF Verifier
Before delving into the main program, it's crucial to understand the role of the eBPF verifier. The eBPF verifier is a component responsible for ensuring the safety and security of eBPF programs. It analyzes the code and enforces rules to prevent potentially unsafe operations, such as accessing forbidden memory regions.
It also does some basic checks on program correctness. Let’s take a look at an example.
Consider a function that checks if a string ends with a specific suffix.
static bool ends_with(char *str1, char *str2) {
int len1 = 0;
int len2 = 0;
while (str1[len1++]);
while (str2[len2++]);
if (len2-- > len1--)
return false;
while (len2 >= 0) {
if (str1[len1] != str2[len2])
return false;
len1--;
len2--;
}
return true;
}
If you try to compile it, the verifier will produce an output looking like this:
; while (str1[len1++]);
69: (71) r2 = *(u8 *)(r2 +0)
invalid read from stack R2 off=0 size=1
processed 25993 insns (limit 1000000) max_states_per_insn 4 total_states 1720 peak_states 49 mark_read 7
This is due to potential unsafe memory access: Imagine we’ve passed an incorrect string that is not null-terminated. The code could then potentially overflow. To make the verifier happy, we should add loop bounds like this:
while (str1[len1++] && len1 < 256);
Monitoring Dynamic Linker Hijacking With eBPF
Now, let's look at the main eBPF program that traces the openat syscall and focuses on attempts to modify /etc/ld.so.preload:
#!/usr/bin/python3
import json
import ctypes as ct
from bcc import BPF, DEBUG_SOURCE
# eBPF program code
bpf_program = r"""
#define O_WRONLY 01
#define O_RDWR 02
#define TEST_PATH_LIMIT 256
static bool ends_with(char *str1, char *str2) {
int len1 = 0;
int len2 = 0;
while (str1[len1++] && len1 < TEST_PATH_LIMIT);
while (str2[len2++] && len2 < TEST_PATH_LIMIT);
if (len2-- > len1--)
return false;
while (len2 >= 0) {
if (str1[len1] != str2[len2])
return false;
len1--;
len2--;
}
return true;
}
TRACEPOINT_PROBE(syscalls, sys_enter_openat) {
char* TRACED_PATH_SUFFIXES[] = { "ld.so.preload" };
char filename[TEST_PATH_LIMIT];
bpf_probe_read_user_str(&filename, sizeof(filename), args->filename);
if (!(args->flags & O_WRONLY) && !(args->flags & O_RDWR)) {
return 0;
}
for (int i = 0; i < sizeof(TRACED_PATH_SUFFIXES) / sizeof(TRACED_PATH_SUFFIXES[0]); i++) {
if (ends_with(filename, TRACED_PATH_SUFFIXES[i])) {
bpf_trace_printk("Catch writing to file: %s\n", filename);
return 0;
}
}
return 0;
}
"""
# See debug flags with explanations here: https://github.com/iovisor/bcc/blob/master/src/python/bcc/__init__.py
b = BPF(text=bpf_program, debug=DEBUG_SOURCE)
print("Started tracing\n")
while 1:
try:
(task, pid, cpu, flags, ts, msg) = b.trace_fields()
except ValueError:
continue
except KeyboardInterrupt:
exit();
print(msg)
In this program, we use the TRACEPOINT_PROBE macro to intercept the sys_enter_openat syscall. We then check if the file being opened has a suffix matching one of the tracked path suffixes, such as "ld.so.preload." If a match is found, a trace message is printed to the console.
Running the eBPF Program
To run the eBPF program, save it in a file named monitor_dyn_linker_hijacking.py and execute:
sudo python3 monitor_dyn_linker_hijacking.py
Now Open a second terminal window and simulate file modification attempts:
sudo touch /etc/ld.so.preload
Observe the output in the first terminal where the eBPF program is running.
Started tracing
b'Catch writing to file: /etc/ld.so.preload'
Conclusion
Having showcased that eBPF can effectively monitor Dynamic Linker Hijacking attempts, specifically alterations to the /etc/ld.so.preload file, we invite you to explore further. Now that you've witnessed eBPF's capability to intercept syscalls and observed examples of how malware employs preloaded libraries for code injection into trusted program flows, feel free to experiment with various file modification scenarios. This hands-on exploration will deepen your understanding of eBPF's versatile capabilities in enhancing system security.
Opinions expressed by DZone contributors are their own.
Comments