mirror of
https://github.com/h3xduck/TripleCross.git
synced 2025-12-16 23:33:06 +08:00
Revision of complete document + Abstract
This commit is contained in:
@@ -45,6 +45,15 @@
|
||||
url = {https://media.defcon.org/DEF%20CON%2029/DEF%20CON%2029%20presentations/Guillaume%20Fournier%20Sylvain%20Afchain%20Sylvain%20Baubeau%20-%20eBPF%2C%20I%20thought%20we%20were%20friends.pdf}
|
||||
},
|
||||
|
||||
@proceedings{ebpf_friends_23,
|
||||
institution = {Datadog},
|
||||
author = {Guillaume Fournier, Sylvain Afchain},
|
||||
organization= {DEFCON 29},
|
||||
page={23},
|
||||
eventtitle = {Cyber Threats 2021: A year in Retrospect},
|
||||
url = {https://media.defcon.org/DEF%20CON%2029/DEF%20CON%2029%20presentations/Guillaume%20Fournier%20Sylvain%20Afchain%20Sylvain%20Baubeau%20-%20eBPF%2C%20I%20thought%20we%20were%20friends.pdf}
|
||||
},
|
||||
|
||||
@proceedings{ebpf_friends_54,
|
||||
institution = {Datadog},
|
||||
author = {Guillaume Fournier, Sylvain Afchain},
|
||||
@@ -1063,6 +1072,23 @@ Userland Linux Rootkits},
|
||||
url={https://github.com/h3xduck/TripleCross}
|
||||
},
|
||||
|
||||
@online{repo_simple_timer,
|
||||
title={simple\_timer.c},
|
||||
url={https://github.com/h3xduck/TripleCross/blob/master/src/helpers/simple_timer.c}
|
||||
},
|
||||
|
||||
@online{repo_execve_hijack,
|
||||
title={simple\_timer.c},
|
||||
url={https://github.com/h3xduck/TripleCross/blob/master/src/helpers/execve_hijack.c}
|
||||
},
|
||||
|
||||
@online{downgrade_attack,
|
||||
title={What is a downgrade attack and how to prevent it},
|
||||
author={Borislav Kiprin},
|
||||
date={2022-04-18},
|
||||
url={https://crashtest-security.com/downgrade-attack/}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -135,12 +135,12 @@ possibility of overwriting the memory of a running process and executing
|
||||
arbitrary code on it.
|
||||
%
|
||||
Subsequent talks on 2021 by Pat Hogan at DEFCON 29 \cite{bad_ebpf}, and by
|
||||
Guillaume Fournier and Sylvain Afchainthe from Datadog at DEFCON 29
|
||||
Guillaume Fournier and Sylvain Afchain from Datadog at DEFCON 29
|
||||
\cite{ebpf_friends}, research deeper on eBPF's ability to support rootkit
|
||||
capabilities. In particular, Hogan shows how eBPF can be used to hide the
|
||||
rootkit's presence from the user and to modify data at system calls, while
|
||||
Fournier and Afchainthe built the first instance of an eBPF-based backdoor
|
||||
with command-and-control(C2) capabilities, enabling to communicate with
|
||||
Fournier and Afchain built the first instance of an eBPF-based backdoor
|
||||
with command-and-control (C2) capabilities, enabling to communicate with
|
||||
the malicious eBPF program by sending network packets to the compromised
|
||||
machine.
|
||||
|
||||
@@ -272,7 +272,7 @@ on each of these pillars relevant in our context:
|
||||
\end{itemize}
|
||||
\item With respect to the 'Protect' pillar, it describes the need for Identify Management, Authentication and Access Control, together with the use of Protective Technologies, between others:
|
||||
\begin{itemize}
|
||||
\item With respect to Identify Management, Authentication and Access Control, the framework describes the need to use the principle of least privilege and management of access permissions, that is, assigning the least permissions possible to each system component. In the case of our rootkit, this is particularly relevant given that it needs to be executed as root or by an user with specific capabilities, as we will explain in section \ref{section:ebpf_security}.
|
||||
\item With respect to Identify Management, Authentication and Access Control, the framework describes the need to use the principle of least privilege and management of access permissions, that is, assigning the least permissions possible to each system component. In the case of our rootkit, this is particularly relevant given that it needs to be executed as root or by an user with specific capabilities, as we will explain in Section \ref{section:ebpf_security}.
|
||||
\item Protective Techniques are solutions with the aim of managing the security of systems and organization assets. In this category we can find the storage of log records about activity on the system, and the protection of communication in the organization network. In the case of our rootkit, maintaining logs and non-plaintext connection means the rootkit shall increase its complexity and invest some resources into stealth functionalities.
|
||||
\end{itemize}
|
||||
\item With respect to the 'Detection' pillar, the framework describes the need for an Anomalies and Events policy and Security Continuous Monitoring, between others.
|
||||
@@ -293,7 +293,7 @@ on each of these pillars relevant in our context:
|
||||
|
||||
|
||||
\subsection{MITRE ATT\&CK}
|
||||
MITRE Adversarial Tactics, Techniques, and Common Knowledge (MITRE ATT\&CK) is a framework collecting knowledge about adversarial techniques, that is, techniques, methodologies and offensive actions followed by threat actors that can be used against computer systems. This is an useful framework for red teaming or pentesting activities performing adversary emulation exercises, since it details adversary behaviours and the techniques being used, which can help to build multiple attack scenarios. Moreover, it is also relevant for professionals in charge of defending a system, since they can prepare and design mitigations for the techniques described in the framework \cite{mitre_blog}\cite{mitre_blog_2}.
|
||||
MITRE Adversarial Tactics, Techniques, and Common Knowledge (MITRE ATT\&CK) is a framework collecting knowledge about adversarial techniques, that is, techniques, methodologies and offensive actions followed by threat actors that can be used against computer systems. This is an useful framework for red teaming or pentesting activities performing adversary emulation exercises, since it details adversary behaviours and the techniques being used, which can help to build multiple attack scenarios. Moreover, it is also relevant for professionals in charge of defending a system, since they can prepare and design mitigations for the techniques described in the framework \cite{mitre_blog} \cite{mitre_blog_2}.
|
||||
|
||||
A relevant aspect of the MITRE ATT\&CK framework is the MITRE ATT\&CK Matrix, which contains all the adversarial techniques organized as 'tactics'. These tactics are the objective of the adversary, which it aims to achieve by using each corresponding technique. Therefore, the MITRE ATT\&CK Matrix shows a list of columns, where each column is one tactic (one adversary objective), and each row on that column shows the techniques associated to that tactic, explaining how that objective can be achieved. Additionally, different matrices exist depending on the platform. In this project, we will consider the Linux Matrix \cite{mitre_matrix_linux}.
|
||||
|
||||
@@ -342,7 +342,7 @@ This section details the structure of this document and the contents of each cha
|
||||
|
||||
\section{Code availability}
|
||||
%Is it ok to reference the repo as a cite? Maybe it's better writing the link directly?
|
||||
All the source code belonging to the rootkit development can be visited publicly at the GitHub repository \cite{triplecross_github}. The most important folders and files of this repository are described in Table \ref{table:triplecross_dirs}.
|
||||
All the source code belonging to the rootkit development can be visited publicly at the GitHub repository \url{https://github.com/h3xduck/TripleCross} \cite{triplecross_github}. The most important folders and files of this repository are described in Table \ref{table:triplecross_dirs}.
|
||||
|
||||
%I can go with more detail if needed. Is it needed?
|
||||
\begin{table}[htbp]
|
||||
@@ -372,5 +372,5 @@ src/vmlinux & Headers containing the definition of kernel data structures (this
|
||||
\label{table:triplecross_dirs}
|
||||
\end{table}
|
||||
|
||||
Additionally, the source code of the RawTCP\_Lib library can be visited publicly at its own GitHub directory \cite{rawtcp_lib}.
|
||||
Additionally, the source code of the RawTCP\_Lib library can be visited publicly at its own GitHub directory \url{https://github.com/h3xduck/RawTCP_Lib} \cite{rawtcp_lib}.
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ Firstly, we will analyse the origins of the eBPF technology, understanding what
|
||||
Finally, we will offer an overview into multiple aspects of the Linux system (memory, networking and executable files), which will be critical during the design of the offensive techniques incorporated in our rootkit.
|
||||
|
||||
\section{BPF}
|
||||
% Is it ok to have sections / chapters without individual intros?
|
||||
In this section we will detail the origins of eBPF in the Linux kernel. By offering us background into the earlier versions of the system, the goal is to acquire insight on the design decisions included in modern versions of eBPF.
|
||||
|
||||
\subsection{Introduction to the BPF system}
|
||||
@@ -36,7 +35,7 @@ In a technical level, BPF comprises both the BPF filter programs developed by th
|
||||
|
||||
|
||||
\subsection{Analysis of a BPF filter program} \label{subsection:analysis_bpf_filter_prog}
|
||||
As we mentioned in section \ref{subsection:bpf_vm}, the components of the BPF VM are used to support running BPF filter programs. A BPF filter is implemented as a boolean function:
|
||||
As we mentioned in Section \ref{subsection:bpf_vm}, the components of the BPF VM are used to support running BPF filter programs. A BPF filter is implemented as a boolean function:
|
||||
\begin{itemize}
|
||||
\item If it returns \textit{true}, the kernel copies the packet to the application.
|
||||
\item If it returns \textit{false}, the packet is not accepted by the filter (and thus the network stack will be the next to operate it).
|
||||
@@ -65,9 +64,9 @@ In order to implement the CFG to be run at the BPF VM, BPF filter programs are m
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|c|c|c|c|c|}
|
||||
\hline
|
||||
& OPCODE & JT & JF & K\\
|
||||
& \textbf{OPCODE} & \textbf{JT} & \textbf{JF} & \textbf{K}\\
|
||||
\hline
|
||||
BITS & 16 & 8 & 8 & 32\\
|
||||
\textbf{BITS} & 16 & 8 & 8 & 32\\
|
||||
\hline
|
||||
\end{tabular}
|
||||
\caption{BPF instruction format.}
|
||||
@@ -98,7 +97,7 @@ Figure \ref{fig:bpf_instructions} shows how BPF instructions are defined accordi
|
||||
\label{fig:bpf_instructions}
|
||||
\end{figure}
|
||||
|
||||
The column \textit{addr modes} in figure \ref{fig:bpf_instructions} describes how the parameters of a BPF instruction are referenced depending on the opcode. The address modes are detailed in figure \ref{fig:bpf_address_mode}. As it can be observed, parameters may consist of immediate values, offsets to memory positions or on the packet, the index register or combinations of the previous.
|
||||
The column \textit{addr modes} in Figure \ref{fig:bpf_instructions} describes how the parameters of a BPF instruction are referenced depending on the opcode. The address modes are detailed in Figure \ref{fig:bpf_address_mode}. As it can be observed, parameters may consist of immediate values, offsets to memory positions or on the packet, the index register or combinations of the previous.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -119,14 +118,14 @@ At the time, by filtering packets before they are handled by the kernel instead
|
||||
\label{fig:bpf_tcpdump_example}
|
||||
\end{figure}
|
||||
|
||||
In figure \ref{fig:bpf_tcpdump_example} we can see how tcpdump sets a filter to display traffic directed to all interfaces (\textit{-i any}) directed to port 80. Flag \textit{-d} instructs tcpdump to display BPF bytecode.
|
||||
In Figure \ref{fig:bpf_tcpdump_example} we can see how tcpdump sets a filter to display traffic directed to all interfaces (\textit{-i any}) directed to port 80. Flag \textit{-d} instructs tcpdump to display BPF bytecode.
|
||||
|
||||
In the example, using the \textit{jf} and \textit{jt} fields, we can label the nodes of the CFG described by the BPF filter. Figure \ref{fig:tcpdump_ex_sol} describes the shortest graph path that a true comparison will need to follow to be accepted by the filter. Note how instruction 010 is checking the value 80, the one our filter is looking for in the port.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
\includegraphics[width=6cm]{cBPF_prog_ex_sol.png}
|
||||
\caption{Shortest path in the CFG described in the example of figure \ref{fig:bpf_tcpdump_example} that a packet needs to follow to be accepted by the BPF filter set with \textit{tcpdump}.}
|
||||
\caption{Shortest path in the CFG described in the code shown in Figure \ref{fig:bpf_tcpdump_example} that a packet needs to follow to be accepted by the BPF filter.}
|
||||
\label{fig:tcpdump_ex_sol}
|
||||
\end{figure}
|
||||
|
||||
@@ -138,7 +137,7 @@ The addition of classic BPF in the Linux kernel set the foundations of eBPF, but
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|c|c|c|}
|
||||
\hline
|
||||
Description & Kernel version & Year\\
|
||||
\textbf{DESCRIPTION} & \textbf{KERNEL VERSION} & \textbf{YEAR}\\
|
||||
\hline
|
||||
\hline
|
||||
\textit{BPF}: First addition in the kernel & 2.1.75 & 1997\\
|
||||
@@ -154,7 +153,7 @@ Description & Kernel version & Year\\
|
||||
|
||||
\hline
|
||||
\end{tabular}
|
||||
\caption{Relevant eBPF updates. Note that only those relevant for our research objectives are shown. This is a selection of the official complete table at \cite{ebpf_funcs_by_ver}.}
|
||||
\caption{Relevant eBPF updates. Selection of the official complete table at \cite{ebpf_funcs_by_ver}.}
|
||||
\label{table:ebpf_history}
|
||||
\end{table}
|
||||
|
||||
@@ -178,32 +177,43 @@ Table \ref{table:ebpf_inst_format} shows the new instruction format for eBPF pro
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|c|c|c|c|c|c|}
|
||||
\hline
|
||||
& IMM & OFF & SRC & DST & OPCODE \\
|
||||
& \textbf{IMM} & \textbf{OFF} & \textbf{SRC} & \textbf{DST} & \textbf{OPCODE} \\
|
||||
\hline
|
||||
BITS & 32 & 16 & 4 & 4 & 8\\
|
||||
\textbf{BITS} & 32 & 16 & 4 & 4 & 8\\
|
||||
\hline
|
||||
\end{tabular}
|
||||
\caption{eBPF instruction format.}
|
||||
\label{table:ebpf_inst_format}
|
||||
\end{table}
|
||||
|
||||
With respect to the BPF VM registers, they get extended from 32 to 64 bits of length, and the number of registers is incremented to 10, instead of the original accumulator and index registers. These registers are also adapted to be similar to those in assembly, as it is shown in table \ref{table:ebpf_regs}.
|
||||
With respect to the BPF VM registers, they get extended from 32 to 64 bits of length, and the number of registers is incremented to 10, instead of the original accumulator and index registers. These registers are also adapted to be similar to those in assembly, as it is shown in Table \ref{table:ebpf_regs}.
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|c|c|m{21em}|}
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{2.5cm}|>{\centering\arraybackslash}p{2.5cm}|>{\centering\arraybackslash}p{7cm}|}
|
||||
\hline
|
||||
\textbf{eBPF REGISTER} & \textbf{x86\_64 REGISTER} & \textbf{PURPOSE}\\
|
||||
\hline
|
||||
eBPF register & x86\_64 register & Purpose\\
|
||||
\hline
|
||||
r0 & rax & Return value from functions and exit value of eBPF programs\\
|
||||
\hline
|
||||
r1 & rdi & Function call argument 1\\
|
||||
\hline
|
||||
r2 & rsi & Function call argument 2\\
|
||||
\hline
|
||||
r3 & rdx & Function call argument 3\\
|
||||
\hline
|
||||
r4 & rcx & Function call argument 4\\
|
||||
\hline
|
||||
r5 & r8 & Function call argument 5\\
|
||||
\hline
|
||||
r6 & rbx & Callee saved register, value preserved between calls\\
|
||||
\hline
|
||||
r7 & r13 & Callee saved register, value preserved between calls\\
|
||||
\hline
|
||||
r8 & r14 & Callee saved register, value preserved between calls\\
|
||||
\hline
|
||||
r9 & r15 & Callee saved register, value preserved between calls\\
|
||||
\hline
|
||||
r10 & rbp & Frame pointer for stack, read only\\
|
||||
\hline
|
||||
\end{tabular}
|
||||
@@ -212,7 +222,7 @@ r10 & rbp & Frame pointer for stack, read only\\
|
||||
\end{table}
|
||||
|
||||
\subsection{JIT compilation}
|
||||
We mentioned in subsection \ref{subsection:ebpf_inst_set} that eBPF registers and instructions describe an almost one-to-one correspondence to those in x86 assembly. This is in fact not a coincidence, but rather it is with the purpose of improving a functionality that was included in Linux kernel 3.0, called Just-in-Time (JIT) compilation \cite{ebpf_JIT} \cite{ebpf_JIT_demystify_page13}.
|
||||
We mentioned in Section \ref{subsection:ebpf_inst_set} that eBPF registers and instructions describe an almost one-to-one correspondence to those in x86 assembly. This is in fact not a coincidence, but rather it is with the purpose of improving a functionality that was included in Linux kernel 3.0, called Just-in-Time (JIT) compilation \cite{ebpf_JIT} \cite{ebpf_JIT_demystify_page13}.
|
||||
|
||||
JIT compiling is an extra step that optimizes the execution speed of eBPF programs. It consists of translating BPF bytecode into machine-specific instructions, so that they run as fast as native code in the kernel. Machine instructions are generated during runtime, written directly into executable memory and executed there \cite{ebpf_JIT_demystify_page14}.
|
||||
|
||||
@@ -222,7 +232,7 @@ The programs developed during this project will always have JIT compiling active
|
||||
|
||||
|
||||
\subsection{The eBPF verifier} \label{subsection:ebpf_verifier}
|
||||
We introduced in figure \ref{fig:ebpf_architecture} the presence of the so-called eBPF verifier. Provided that we will be loading programs in the kernel from user space, these programs need to be checked for safety before being valid to be executed.
|
||||
We introduced in Figure \ref{fig:ebpf_architecture} the presence of the so-called eBPF verifier. Provided that we will be loading programs in the kernel from user space, these programs need to be checked for safety before being valid to be executed.
|
||||
|
||||
The verifier performs a series of tests which every eBPF program must pass in order to be accepted. Otherwise, user programs could leak privileged data, result in kernel memory corruption, or hang the kernel in an infinite loop, between others. Therefore, the verifier limits multiple aspects of eBPF programs so that they are restricted to the intended functionality, whilst at the same time offering a reasonable amount of freedom to the developer.
|
||||
|
||||
@@ -249,16 +259,20 @@ An eBPF map is a generic storage for eBPF programs used to share data between us
|
||||
|
||||
A map consists of a key + value tuple. Both fields can have an arbitrary data type, the map only needs to know the length of the key and the value field at its creation \cite{bpf_syscall}. Programs can open maps by specifying their ID, and lookup or delete elements in the map by specifying its key, also insert new ones by supplying the element value and they key to store it with.
|
||||
|
||||
Therefore, creating a map requires a struct with the fields shown in table \ref{table:ebpf_map_struct}.
|
||||
Therefore, creating a map requires a struct with the fields shown in Table \ref{table:ebpf_map_struct}.
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|c|c|}
|
||||
\hline
|
||||
FIELD & VALUE\\
|
||||
\textbf{FIELD} & \textbf{VALUE}\\
|
||||
\hline
|
||||
\hline
|
||||
type & Type of eBPF map. Described in table \ref{table:ebpf_map_types}\\
|
||||
\hline
|
||||
key\_size & Size of the data structure to use as a key\\
|
||||
\hline
|
||||
value\_size & Size of the data structure to use as value field\\
|
||||
\hline
|
||||
max\_entries & Maximum number of elements in the map\\
|
||||
\hline
|
||||
\end{tabular}
|
||||
@@ -269,18 +283,22 @@ max\_entries & Maximum number of elements in the map\\
|
||||
Table \ref{table:ebpf_map_types} describes the main types of eBPF maps that are available for use. During the development of our rootkit, we will mainly focus on hash maps (BPF\_MAP\_TYPE\_HASH), provided that they are simple to use and we do not require of any special storage for our research purposes.
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|c|>{\centering\arraybackslash}p{10cm}|}
|
||||
\begin{tabular}{|c|>{\centering\arraybackslash}p{7cm}|}
|
||||
\hline
|
||||
\textbf{TYPE} & \textbf{DESCRIPTION}\\
|
||||
\hline
|
||||
TYPE & DESCRIPTION\\
|
||||
\hline
|
||||
BPF\_MAP\_TYPE\_HASH & A hast table-like storage, elements are stored in tuples.\\
|
||||
\hline
|
||||
BPF\_MAP\_TYPE\_ARRAY & Elements are stored in an array.\\
|
||||
BPF\_MAP\_TYPE\_RINGBUF & Map providing alerts from kernel to user space, covered in subsection \ref{subsection:bpf_ring_buf}\\
|
||||
\hline
|
||||
BPF\_MAP\_TYPE\_RINGBUF & Map providing alerts from kernel to user space, covered in Section \ref{subsection:bpf_ring_buf}\\
|
||||
\hline
|
||||
BPF\_MAP\_TYPE\_PROG\_ARRAY & Stores descriptors of eBPF programs\\
|
||||
\hline
|
||||
\hline
|
||||
\end{tabular}
|
||||
\caption{Types of eBPF maps. Only those used in our rootkit are displayed, the full list can be consulted in the man page \cite{bpf_syscall}}
|
||||
\caption{Relevant types of eBPF maps. Full list can be consulted in the man page \cite{bpf_syscall}}
|
||||
\label{table:ebpf_map_types}
|
||||
\end{table}
|
||||
|
||||
@@ -292,12 +310,12 @@ eBPF ring buffers are a special kind of eBPF maps, providing a one-way direction
|
||||
\subsection{The bpf() syscall} \label{subsection:bpf_syscall}
|
||||
The bpf() syscall is used to issue commands from user space to kernel space in eBPF programs. This syscall is multiplexor, meaning that it can perform a great range of actions, changing its behaviour depending on the parameters.
|
||||
|
||||
The main operations that can be issued are described in table \ref{table:ebpf_syscall}:
|
||||
The main operations that can be issued are described in Table \ref{table:ebpf_syscall}:
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|c|>{\centering\arraybackslash}p{5cm}|>{\centering\arraybackslash}p{5cm}|}
|
||||
\begin{tabular}{|c|>{\centering\arraybackslash}p{4cm}|>{\centering\arraybackslash}p{4cm}|}
|
||||
\hline
|
||||
COMMAND & ATTRIBUTES & DESCRIPTION\\
|
||||
\textbf{COMMAND} & \textbf{ATTRIBUTES} & \textbf{DESCRIPTION}\\
|
||||
\hline
|
||||
\hline
|
||||
BPF\_MAP\_CREATE & Struct with map info as defined in table \ref{table:ebpf_map_struct} & Create a new map\\
|
||||
@@ -311,16 +329,16 @@ BPF\_MAP\_DELETE\_ELEM & Map ID and struct with key to search in the map & Delet
|
||||
BPF\_PROG\_LOAD & Struct describing the type of eBPF program to load & Load an eBPF program in the kernel\\
|
||||
\hline
|
||||
\end{tabular}
|
||||
\caption{Types of syscall actions. Only those relevant to our research are shown the full list and attribute details can be consulted in the man page \cite{bpf_syscall}}
|
||||
\caption{Relevant types of syscall actions. Full list and attribute details can be consulted in the man page \cite{bpf_syscall}}
|
||||
\label{table:ebpf_syscall}
|
||||
\end{table}
|
||||
|
||||
With respect to the program type indicated with BPF\_PROG\_LOAD, this parameter indicates the type of eBPF program, setting the context in the kernel in which it will run, and to which modules it will have access to. The types of programs relevant for our research are described in table \ref{table:ebpf_prog_types}.
|
||||
With respect to the program type indicated with BPF\_PROG\_LOAD, this parameter indicates the type of eBPF program, setting the context in the kernel in which it will run, and to which modules it will have access to. The types of programs relevant for our research are described in Table \ref{table:ebpf_prog_types}.
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|c|>{\centering\arraybackslash}p{5cm}|}
|
||||
\hline
|
||||
PROGRAM TYPE & DESCRIPTION\\
|
||||
\textbf{PROGRAM TYPE} & \textbf{DESCRIPTION}\\
|
||||
\hline
|
||||
\hline
|
||||
BPF\_PROG\_TYPE\_KPROBE & Program to instrument code to an attached kprobe\\
|
||||
@@ -338,7 +356,7 @@ BPF\_PROG\_TYPE\_SCHED\_CLS & Program to filter, redirect and monitor events usi
|
||||
\label{table:ebpf_prog_types}
|
||||
\end{table}
|
||||
|
||||
In section \ref{section:TODO}, we will proceed to analyse in detail the different program types and what capabilities` they offer.
|
||||
In Section \ref{section:ebpf_prog_types}, we will proceed to analyse in detail the different program types and what capabilities they offer.
|
||||
|
||||
\subsection{eBPF helpers} \label{subsection:ebpf_helpers}
|
||||
Our last component to cover of the eBPF architecture are the eBPF helpers. Since eBPF programs have limited accessibility to kernel functions (which kernel modules commonly have free access to), the eBPF system offers a set of limited functions called helpers \cite{ebpf_helpers}, which are used by eBPF programs to perform certain actions and interact with the context on which they are run. The list of helpers a program can call varies between eBPF program types, since different programs run in different contexts.
|
||||
@@ -348,9 +366,9 @@ It is important to highlight that, just like commands issued via the bpf() sysca
|
||||
Table \ref{table:ebpf_helpers} lists the most relevant general-purpose eBPF helpers we will use during the development of our project. We will later detail those helpers exclusive to an specific eBPF program type in the sections on which they are studied.
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|c|>{\centering\arraybackslash}p{10cm}|}
|
||||
\begin{tabular}{|c|>{\centering\arraybackslash}p{8cm}|}
|
||||
\hline
|
||||
eBPF helper & DESCRIPTION\\
|
||||
\textbf{eBPF HELPER}& \textbf{DESCRIPTION}\\
|
||||
\hline
|
||||
\hline
|
||||
bpf\_map\_lookup\_elem() & Query an element with a certain key in a map\\
|
||||
@@ -378,7 +396,7 @@ bpf\_ringbuf\_submit() & Submit data to an specific eBPF ring buffer, and notify
|
||||
bpf\_tail\_call() & Jump to another eBPF program preserving the current stack\\
|
||||
\hline
|
||||
\end{tabular}
|
||||
\caption{Common eBPF helpers. Only those relevant to our research are shown. Those helpers exclusive to an specific program type are not listed. The full list and attribute details can be consulted in the man page \cite{ebpf_helpers}.}
|
||||
\caption{Relevant common eBPF helpers. Those helpers exclusive to an specific program type are not listed. Full list and attribute details can be consulted in the man page \cite{ebpf_helpers}.}
|
||||
\label{table:ebpf_helpers}
|
||||
\end{table}
|
||||
|
||||
@@ -387,7 +405,7 @@ bpf\_tail\_call() & Jump to another eBPF program preserving the current stack\\
|
||||
In the previous subsection \ref{subsection:bpf_syscall} we introduced the new types of eBPF programs that are supported and that we will be developing for our offensive analysis. In this section, we will analyse in greater detail how eBPF is integrated in the Linux kernel in order to support these new functionalities.
|
||||
|
||||
\subsection{XDP} \label{subsection:xdp}
|
||||
EXpress Data Path (XDP) programs are a novel type of eBPF program that allows for the lowest-latency traffic filtering and monitoring in the whole Linux kernel. In order to load an XDP program, a bpf() syscall with the command BPF\_PROG\_LOAD and the program type BPF\_PROG\_TYPE\_XDP must be issued.
|
||||
Express Data Path (XDP) programs are a novel type of eBPF program that allows for the lowest-latency traffic filtering and monitoring in the whole Linux kernel. In order to load an XDP program, a bpf() syscall with the command BPF\_PROG\_LOAD and the program type BPF\_PROG\_TYPE\_XDP must be issued.
|
||||
|
||||
These programs are directly attached to the Network Interface Controller (NIC) driver, and thus they can process the packet before any other module \cite{xdp_gentle_intro}.
|
||||
|
||||
@@ -409,7 +427,7 @@ Figure \ref{fig:xdp_diag} shows how XDP is integrated in the network processing
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|c|>{\centering\arraybackslash}p{10cm}|}
|
||||
\hline
|
||||
ACTION & DESCRIPTION\\
|
||||
\textbf{ACTION} & \textbf{DESCRIPTION}\\
|
||||
\hline
|
||||
\hline
|
||||
XDP\_PASS & Let packet proceed with operated modifications on it.\\
|
||||
@@ -423,11 +441,11 @@ XDP\_DROP & Drops the packet completely, kernel networking will not be notified.
|
||||
\label{table:xdp_actions_av}
|
||||
\end{table}
|
||||
|
||||
Some of the XDP-exclusive eBPF helpers we will be discussing in later sections are shown in table \ref{table:xdp_helpers}.
|
||||
Some of the XDP-exclusive eBPF helpers we will be discussing in later sections are shown in Table \ref{table:xdp_helpers}.
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|c|>{\centering\arraybackslash}p{10cm}|}
|
||||
\begin{tabular}{|c|>{\centering\arraybackslash}p{8cm}|}
|
||||
\hline
|
||||
eBPF helper & DESCRIPTION\\
|
||||
\textbf{eBPF HELPER} & \textbf{DESCRIPTION}\\
|
||||
\hline
|
||||
\hline
|
||||
bpf\_xdp\_adjust\_head() & Enlarges or reduces the extension of a packet, by moving the address of its first byte.\\
|
||||
@@ -441,7 +459,7 @@ bpf\_xdp\_adjust\_tail() & Enlarges or reduces the extension of a packet, by mov
|
||||
|
||||
|
||||
\subsection{Traffic Control} \label{subsection:tc}
|
||||
Traffic Control (TC) programs are also indicated for networking instrumentation. Similarly to XDP, their module is positioned before entering the overall network processing of the kernel. However, as it can be observed in figure \ref{fig:xdp_diag}, they differ in some aspects:
|
||||
Traffic Control (TC) programs are also indicated for networking instrumentation. Similarly to XDP, their module is positioned before entering the overall network processing of the kernel. However, as it can be observed in Figure \ref{fig:xdp_diag}, they differ in some aspects:
|
||||
\begin{itemize}
|
||||
\item TC programs receive a network buffer with metadata (in the figure, \textit{sk\_buff}) about the packet in it. This renders TC programs less ideal than XDP for performing large packet modifications (like new headers), but at the same time the additional metadata fields make it easier to locate and modify specific packet fields \cite{tc_differences}.
|
||||
\item TC programs can be attached to the \textit{ingress} or \textit{egress} points, meaning that an eBPF program can operate not only over incoming traffic, but also over the outgoing packets.
|
||||
@@ -453,12 +471,12 @@ With respect to how TC programs operate, the Traffic Control system in Linux is
|
||||
\item Our TC eBPF program is attached to the qdisc. It will work as a filter, being run for every of the packets dispatched by the qdisc.
|
||||
\end{enumerate}
|
||||
|
||||
Similarly to XDP, the TC eBPF programs can decide an action to be executed on a packet by specifying a return value. These actions are almost analogous to the ones in XDP, as it can be observed in table \ref{table:tc_actions}.
|
||||
Similarly to XDP, the TC eBPF programs can decide an action to be executed on a packet by specifying a return value. These actions are almost analogous to the ones in XDP, as it can be observed in Table \ref{table:tc_actions}.
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|c|>{\centering\arraybackslash}p{10cm}|}
|
||||
\begin{tabular}{|c|>{\centering\arraybackslash}p{9cm}|}
|
||||
\hline
|
||||
ACTION & DESCRIPTION\\
|
||||
\textbf{ACTION} & \textbf{DESCRIPTION}\\
|
||||
\hline
|
||||
\hline
|
||||
TC\_ACT\_OK & Let packet proceed with operated modifications on it.\\
|
||||
@@ -472,11 +490,11 @@ TC\_ACT\_SHOT & Drops the packet completely, kernel networking will not be notif
|
||||
\label{table:tc_actions}
|
||||
\end{table}
|
||||
|
||||
Finally, as in XDP, there exists a list of useful BPF helpers that will be relevant for the creation of our rootkit. They are shown in table \ref{table:tc_helpers}.
|
||||
Finally, as in XDP, there exists a list of useful BPF helpers that will be relevant for the creation of our rootkit. They are shown in Table \ref{table:tc_helpers}.
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|c|>{\centering\arraybackslash}p{10cm}|}
|
||||
\begin{tabular}{|c|>{\centering\arraybackslash}p{9cm}|}
|
||||
\hline
|
||||
eBPF helper & DESCRIPTION\\
|
||||
\textbf{eBPF HELPER} & \textbf{DESCRIPTION}\\
|
||||
\hline
|
||||
\hline
|
||||
bpf\_l3\_csum\_replace() & Recomputes the network layer 3 (e.g.: IP) checksum of the packet.\\
|
||||
@@ -515,7 +533,7 @@ Kprobes are another tracing technology of the Linux kernel whose functionality h
|
||||
|
||||
As it happened with tracepoints, the probe functions have access to the parameters of the original hooked function. Also, the kernel maintains a list of kernel symbols (addresses) which are relevant for tracing and that offer us insight into which functions we can probe. It can be visited under the file \textit{/proc/kallsyms}, which exports symbols of kernel functions and loaded kernel modules \cite{kallsyms_kernel}.
|
||||
|
||||
Also similarly, since tracepoints could be found in their \textit{enter} and \textit{exit} variations, kprobes have their counterpart, name kretprobes, which call the hooked probe once a return instruction is reached after the hooked symbol. This means that a kretprobe hooked to a kernel function will call the probe function once it exits.
|
||||
Also similarly, since tracepoints could be found in their \textit{enter} and \textit{exit} variations, kprobes have their counterpart, named kretprobes, which call the hooked probe once a return instruction is reached after the hooked symbol. This means that a kretprobe hooked to a kernel function will call the probe function once it exits.
|
||||
|
||||
In eBPF, a program can issue a bpf() syscall with the command BPF\_PROG\_LOAD and the program type BPF\_PROG\_TYPE\_KPROBE, specifying which is the function with the kprobe to attach to and an arbitrary function probe to call when it is hit. This function probe is defined by the user in the eBPF program submitted to the kernel.
|
||||
|
||||
@@ -535,12 +553,12 @@ In eBPF, programs can issue a bpf() syscall with the command BPF\_PROG\_LOAD and
|
||||
|
||||
% Is this the best title?
|
||||
\section{Developing eBPF programs}
|
||||
In section \ref{section:modern_ebpf}, we discussed the overall architecture of the eBPF system which is now an integral part of the Linux kernel. We also studied the process which a piece of eBPF bytecode follows in order to be accepted in the kernel. However, for an eBPF developer, programming bytecode and working with bpf() calls natively is not an easy task, therefore an additional layer of abstraction was needed.
|
||||
In Section \ref{section:modern_ebpf}, we discussed the overall architecture of the eBPF system which is now an integral part of the Linux kernel. We also studied the process which a piece of eBPF bytecode follows in order to be accepted in the kernel. However, for an eBPF developer, programming bytecode and working with bpf() calls natively is not an easy task, therefore an additional layer of abstraction was needed.
|
||||
|
||||
Nowadays, there exist multiple popular alternatives for writing and running eBPF programs. We will overview which they are and proceed to analyse in further detail the option that we will use for the development of our rootkit.
|
||||
|
||||
\subsection{BCC}
|
||||
BPF Compiler Collection (BCC) is one of the first and well-known toolkits for eBPF programming available \cite{bcc_github}. It allows to include eBPF code into user programs. These programs are developed in python, and the eBPF code is embedded as a plain string. An example of a BCC program is included in %TODO ANNEX???
|
||||
BPF Compiler Collection (BCC) is one of the first and well-known toolkits for eBPF programming available \cite{bcc_github}. It allows to include eBPF code into user programs. These programs are developed in Python, and the eBPF code is embedded as a plain string.
|
||||
|
||||
Although BCC offers a wide range of tools to easy the development of eBPF programs, we found it not to be the most appropriate for our large-scale eBPF project. In particular, this was due to the feature of eBPF programs being stored as a python string, which leads to difficult scalability, poor development experience given that programming errors are detected at runtime (once the python program issues the compilation of the string), and simply better features from competing libraries.
|
||||
|
||||
@@ -556,13 +574,13 @@ Bpftool is not a development framework like BCC, but one of the most relevant to
|
||||
|
||||
Although we will not be covering bpftool during our overview on the constructed eBPF rootkit, it was used extensively during the development and became a key tool for debugging eBPF programs, particularly to peek data at eBPF maps during runtime.
|
||||
|
||||
\subsection{Libbpf}
|
||||
\subsection{Libbpf} \label{subsection:libbpf}
|
||||
Libbpf \cite{libbpf_github} is a library for loading and interacting with eBPF programs, which is currently maintained in the Linux kernel source tree \cite{libbpf_upstream}. It is one of the most popular frameworks to develop eBPF applications, both because it makes eBPF programming similar to common kernel development and because it aims at reducing kernel-version dependencies, thus increasing programs portability between systems \cite{libbpf_core}. During our research, however, we will not make use of this functionalities given that a portable program is not in our research goals.
|
||||
|
||||
As we discussed in section \ref{section:modern_ebpf}, eBPF programs are composed of both the eBPF code in the kernel and a user space program that can interact with it. With libbpf, the eBPF kernel program is developed in C (a real program, not a string later compiled as with BCC), while user programs are usually developed in C, Rust or GO. For our project, we will use the C version of libbpf, so both the user and kernel side of our rootkit will be developed in this language.
|
||||
As we discussed in Section \ref{section:modern_ebpf}, eBPF programs are composed of both the eBPF code in the kernel and a user space program that can interact with it. With libbpf, the eBPF kernel program is developed in C (a real program, not a string later compiled as with BCC), while user programs are usually developed in C, Rust or GO. For our project, we will use the C version of libbpf, so both the user and kernel side of our rootkit will be developed in this language.
|
||||
|
||||
% Cites in the following paragraph?
|
||||
When using libbpf with the C language, both the user-side and kernel eBPF program are compiled together using the Clang/LLVM compiler, translating C instructions into eBPF bytecode. As a clarification, Clang is the front-end of the compiler, translating C instructions into an intermediate form understandable by LLVM, whilst LLVM is the back end compiling the intermediate code into eBPF bytecode. As it can be observed in figure \ref{fig:libbpf}, the result of the compilation is a single program, comprising the user-side which will launch a user process, the eBPF bytecode to be run in the kernel, and other structures libbpf generates about eBPF maps and other meta data. This program is encapsulated as an ELF file (a common executable format).
|
||||
When using libbpf with the C language, both the user-side and kernel eBPF program are compiled together using the Clang/LLVM compiler, translating C instructions into eBPF bytecode. As a clarification, Clang is the front-end of the compiler, translating C instructions into an intermediate form understandable by LLVM, whilst LLVM is the back end compiling the intermediate code into eBPF bytecode. As it can be observed in Figure \ref{fig:libbpf}, the result of the compilation is a single program, comprising the user-side which will launch a user process, the eBPF bytecode to be run in the kernel, and other structures libbpf generates about eBPF maps and other meta data. This program is encapsulated as an ELF file (a common executable format).
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -576,9 +594,9 @@ Finally, we will overview one of the main functionalities of libbpf to simplify
|
||||
Table \ref{table:libbpf_skel} describes the API offered by the BPF skeleton. Note that <name> is substituted by the name of the program being compiled.
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|c|>{\centering\arraybackslash}p{10cm}|}
|
||||
\begin{tabular}{|c|>{\centering\arraybackslash}p{9cm}|}
|
||||
\hline
|
||||
Function name & Description\\
|
||||
\textbf{FUNCTION NAME} & \textbf{DESCRIPTION}\\
|
||||
\hline
|
||||
\hline
|
||||
<name>\_\_open() & Parse the eBPF programs and maps.\\
|
||||
@@ -594,17 +612,17 @@ Function name & Description\\
|
||||
\label{table:libbpf_skel}
|
||||
\end{table}
|
||||
|
||||
Note that the BPF skeleton also offers further granularity at the time of dealing with programs, so that individual programs can be loaded or attached instead of all simultaneously. This is the approach we will generally use in the development of our rootkit, as it will be explained in section \ref{subsection:ebpf_progs_config}.
|
||||
Note that the BPF skeleton also offers further granularity at the time of dealing with programs, so that individual programs can be loaded or attached instead of all simultaneously. This is the approach we will generally use in the development of our rootkit, as it will be explained in Section \ref{subsection:ebpf_progs_config}.
|
||||
|
||||
|
||||
|
||||
\section{Security features in eBPF}
|
||||
As we have shown in section \ref{section:modern_ebpf}, eBPF has been an active part of the Linux kernel from its 3.18 version. However, as with many other components of the kernel, its availability to the user depends on the parameters with which the kernel has been compiled. Specifically, eBPF is only available to kernels compiled with the flags specified in table \ref{table:ebpf_kernel_flags}.
|
||||
\section{Security features in eBPF} \label{section:ebpf_security}
|
||||
As we have shown in Section \ref{section:modern_ebpf}, eBPF has been an active part of the Linux kernel from its 3.18 version. However, as with many other components of the kernel, its availability to the user depends on the parameters with which the kernel has been compiled. Specifically, eBPF is only available to kernels compiled with the flags specified in table \ref{table:ebpf_kernel_flags}.
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|c|c|>{\centering\arraybackslash}p{8cm}|}
|
||||
\begin{tabular}{|c|c|>{\centering\arraybackslash}p{7cm}|}
|
||||
\hline
|
||||
Flag & Value & Description\\
|
||||
\textbf{FLAG} & \textbf{VALUE} & \textbf{DESCRIPTION}\\
|
||||
\hline
|
||||
\hline
|
||||
\multicolumn{1}{|c|}{CONFIG\_BPF} & \multicolumn{1}{|c|}{y} & \multirow{2}{*}{Basic BPF compilation (mandatory)}\\
|
||||
@@ -650,7 +668,7 @@ More recently, in an effort to further granulate the permissions needed for load
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{4cm}|>{\centering\arraybackslash}p{10cm}|}
|
||||
\hline
|
||||
Capabilities & eBPF functionality\\
|
||||
\textbf{CAPABILITIES} & \textbf{eBPF FUNCTIONALITY}\\
|
||||
\hline
|
||||
\hline
|
||||
No capabilities & Load and attach BPF\_PROG\_TYPE\_SOCKET\_FILTER, load BPF\_PROG\_TYPE\_CGROUP\_SKB programs.\\
|
||||
@@ -672,12 +690,12 @@ Therefore, eBPF network programs usually require both CAP\_BPF and CAP\_NET\_ADM
|
||||
|
||||
Although for a long time there have existed efforts towards enhancing unprivileged eBPF, it remains a worrying feature \cite{unprivileged_ebpf}. The main issue is that the verifier must be prepared to detect any attempt to extract kernel memory access or user memory modification by unprivileged eBPF programs, which is a complex task. In fact, there have existed numerous security vulnerabilities which allow for privilege escalation using eBPF, that is, execution of privileged eBPF programs by exploiting vulnerabilities in unprivileged eBPF \cite{cve_unpriv_ebpf}.
|
||||
|
||||
This influx of security vulnerabilities leads to the recent inclusion of an attribute into the kernel which allows for setting whether unprivileged eBPF is allowed in the system or not. This parameter is named \textit{kernel.unprivileged\_bpf\_disabled}, its values can be seen in table \ref{table:unpriv_ebpf_values}.
|
||||
This influx of security vulnerabilities leads to the recent inclusion of an attribute into the kernel which allows for setting whether unprivileged eBPF is allowed in the system or not. This parameter is named \textit{kernel.unprivileged\_bpf\_disabled}, its values can be seen in Table \ref{table:unpriv_ebpf_values}.
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{4cm}|>{\centering\arraybackslash}p{10cm}|}
|
||||
\hline
|
||||
Value & Meaning\\
|
||||
\textbf{Value} & \textbf{Meaning}\\
|
||||
\hline
|
||||
\hline
|
||||
0 & Unprivileged eBPF is enabled.\\
|
||||
@@ -750,7 +768,7 @@ Figure \ref{fig:mem_proc_arch} describes how virtual memory is distributed withi
|
||||
\end{itemize}
|
||||
|
||||
\subsection{The process stack} \label{subsection:stack}
|
||||
Between all the sections we identified in a process virtual memory, the stack will be particularly relevant during our research. We will therefore study it now in detail.
|
||||
Among all the sections we identified in a process virtual memory, the stack will be particularly relevant during our research. We will therefore study it now in detail.
|
||||
|
||||
Firstly, we will present how the stack is structured, and which operations can be executed on it. Figure \ref{fig:stack_pres} presents a stack during the execution of a program. Table \ref{table:systemv_abi_other} explains the purpose of the most relevant registers related to the stack and program execution:
|
||||
|
||||
@@ -764,7 +782,7 @@ Firstly, we will present how the stack is structured, and which operations can b
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{2cm}|>{\centering\arraybackslash}p{10cm}|}
|
||||
\hline
|
||||
Register & Purpose\\
|
||||
\textbf{REGISTER} & \textbf{PURPOSE}\\
|
||||
\hline
|
||||
\hline
|
||||
rip & Instruction Pointer - Memory address of the next instruction to execute\\
|
||||
@@ -778,9 +796,9 @@ rbp & Base/Frame Pointer - Memory address of the start of the stack frame\\
|
||||
\label{table:systemv_abi_other}
|
||||
\end{table}
|
||||
|
||||
As it can be observed in figure \ref{fig:stack_pres}, the stack grows towards lower memory addresses, and it is organized in stack frames, delimited by the registers rsp and rbp. An stack frame is a division of the stack which contains all the data (variables, call arguments...) belonging to a single function execution. When a function is exited, its stack frame is removed, and if a function calls a nested function, then its stack frame is preserved and a new stack frame is inserted into the stack.
|
||||
As it can be observed in Figure \ref{fig:stack_pres}, the stack grows towards lower memory addresses, and it is organized in stack frames, delimited by the registers rsp and rbp. An stack frame is a division of the stack which contains all the data (variables, call arguments...) belonging to a single function execution. When a function is exited, its stack frame is removed, and if a function calls a nested function, then its stack frame is preserved and a new stack frame is inserted into the stack.
|
||||
|
||||
As table \ref{table:systemv_abi_other} explains, the rbp and rsp registers are used for keeping track of the starting and final position of the current stack frame respectively. We can see in figure \ref{fig:stack_pres} that their value is a memory address pointing to their stack positions. On the other hand, the rip register does not point to the stack, but rather to the .text section (see figure \ref{fig:mem_proc_arch}), where it points to the next instruction to be executed. However, as we will now see, its value must also be stored in the stack frame when a nested function is called, since after the nested function exits we need to restore the execution in the same instruction of the original function.
|
||||
As Table \ref{table:systemv_abi_other} explains, the rbp and rsp registers are used for keeping track of the starting and final position of the current stack frame respectively. We can see in Figure \ref{fig:stack_pres} that their value is a memory address pointing to their stack positions. On the other hand, the rip register does not point to the stack, but rather to the .text section (see Figure \ref{fig:mem_proc_arch}), where it points to the next instruction to be executed. However, as we will now see, its value must also be stored in the stack frame when a nested function is called, since after the nested function exits we need to restore the execution in the same instruction of the original function.
|
||||
|
||||
As with any LIFO structure, the stack supports two main operations: \textit{push} and \textit{pop}. In the x86\_64 architecture, it operates with chunks of data of either 16, 32 or 64 bytes. Table \ref{fig:stack_ops} shows a representation of these operations in the stack.
|
||||
\begin{itemize}
|
||||
@@ -813,14 +831,14 @@ As we mentioned, the stack stores function parameters, return addresses and loca
|
||||
\end{figure}
|
||||
|
||||
\begin{enumerate}
|
||||
\item The function arguments are pushed into the stack. We can see them in the stack of figure \ref{fig:stack} in reverse order.
|
||||
\item The function arguments are pushed into the stack. We can see them in the stack of Figure \ref{fig:stack} in reverse order.
|
||||
\item The function is called:
|
||||
\begin{enumerate}
|
||||
\item The value of register rip is pushed into the stack, so that it is saved for when the function exists. We can see it on figure \ref{fig:stack} as 'ret'.
|
||||
\item The value of register rip is pushed into the stack, so that it is saved for when the function exists. We can see it on Figure \ref{fig:stack} as 'ret'.
|
||||
\item The value of rip changes to point to the first instruction of the called function.
|
||||
\item We execute what is called as the \textit{function preamble} \cite{8664_params_abi_p18}, which prepares the stack frame for the called function:
|
||||
\begin{enumerate}
|
||||
\item The value of rbp is pushed into the stack, so that we can restore the previous stack frame when the function exits. We can see it on figure \ref{fig:stack} as the 'saved frame pointer'.
|
||||
\item The value of rbp is pushed into the stack, so that we can restore the previous stack frame when the function exits. We can see it on Figure \ref{fig:stack} as the 'saved frame pointer'.
|
||||
\item The value of rsp is moved into rbp. Therefore, now rbp points to the end of the previous stack frame.
|
||||
\item The value of rsp is usually decremented (since the stack needs to go to lower memory addresses) so that we allocate some space for function variables.
|
||||
\end{enumerate}
|
||||
@@ -837,7 +855,7 @@ As we mentioned, the stack stores function parameters, return addresses and loca
|
||||
|
||||
|
||||
\section{Attacks at the stack} \label{section:attacks_stack}
|
||||
In section \ref{subsection:stack}, we studied how the stack works and which is the process that a program follows in order to call a function. As we saw in figure \ref{fig:stack}, the processor pushes into the stack several data which is used to restore the context of the original function once the called function exits. These pushed arguments included:
|
||||
In Section \ref{subsection:stack}, we studied how the stack works and which is the process that a program follows in order to call a function. As we saw in Figure \ref{fig:stack}, the processor pushes into the stack several data which is used to restore the context of the original function once the called function exits. These pushed arguments included:
|
||||
\begin{itemize}
|
||||
\item The arguments with which the function is being called (if they need to be passed in the stack, such as byte arrays).
|
||||
\item The original value of the rip register (ret), to restore the execution on the original function.
|
||||
@@ -852,12 +870,12 @@ Although this process is simple enough, it opens the possibility for an attacker
|
||||
\label{fig:stack_ret_hij_simple}
|
||||
\end{figure}
|
||||
|
||||
In the figure, we can observe how, during the execution of the called function, the attacker overwrites the value of ret in the stack. Once the function exists, as we explained in section \ref{subsection:stack}, during the function epilogue the value of ret will be popped and moved into rip, so that the execution is directed to the original next instruction. However, because the value was modified, the attacker controls which instructions are executed next.
|
||||
In the Figure, we can observe how, during the execution of the called function, the attacker overwrites the value of ret in the stack. Once the function exists, as we explained in Section \ref{subsection:stack}, during the function epilogue the value of ret will be popped and moved into rip, so that the execution is directed to the original next instruction. However, because the value was modified, the attacker controls which instructions are executed next.
|
||||
|
||||
Attackers have historically used multiple techniques to overwrite the ret value in the stack. In this section, we will present two of the most popular techniques, which will be used as a basis for designing our own attacks using eBPF.
|
||||
|
||||
\subsection{Buffer overflow} \label{subsection: buf_overflow}
|
||||
The stack buffer overflow is one of the most popular exploitation techniques to overwrite data at the stack. In this technique, an attacker takes advantage of a program receiving a user value stored in a buffer whose capacity is smaller of that of the supplied value. Code snippet \ref{code:vuln_overflow} shows an example of a vulnerable program:
|
||||
The stack buffer overflow is one of the most popular exploitation techniques to overwrite data at the stack. In this technique, an attacker takes advantage of a program receiving a user value stored in a buffer whose capacity is smaller of that of the supplied value. Code Snippet \ref{code:vuln_overflow} shows an example of a vulnerable program:
|
||||
|
||||
\begin{lstlisting}[language=C, caption={Program vulnerable to buffer overflow.}, label={code:vuln_overflow}]
|
||||
void foo(char *bar){ // bar may be larger than 12 characters
|
||||
@@ -871,7 +889,7 @@ int main(int argc, char *argv[]){
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
During the execution of the above program, since the char array \textit{buffer} is a buffer of length 12 stored in the stack, then if the value of \textit{bar} is larger than 12 bytes it will overflow the allocated space in the stack. This is usually the case of using unsafe functions for processing user input such as strcpy(), which does not check whether the array fits in the buffer. Figure \ref{fig:buffer_overflow} shows how the overflow happens in the stack.
|
||||
During the execution of the above program, since the char array \textit{buffer} is a buffer of length 12 stored in the stack, then if the value of \textit{bar} is larger than 12 bytes it will overflow the allocated space in the stack. This is usually the case of using unsafe functions for processing user input such as strcpy(), which do not check whether the array fits in the buffer. Figure \ref{fig:buffer_overflow} shows how the overflow happens in the stack.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -882,7 +900,7 @@ During the execution of the above program, since the char array \textit{buffer}
|
||||
|
||||
As we can observe in the figure, the new data written into the buffer has also overwritten other fields which were pushed into the stack, such as sfp and ret, resulting in changing the flow of execution once the function exists.
|
||||
|
||||
Usually, an attacker exploiting a program vulnerable to stack buffer overflow is interested in running arbitrary (malicious) code. For this, the attacker follows the process shown in figure \ref{fig:buffer_overflow_shellcode}:
|
||||
Usually, an attacker exploiting a program vulnerable to stack buffer overflow is interested in running arbitrary (malicious) code. For this, the attacker follows the process shown in Figure \ref{fig:buffer_overflow_shellcode}:
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -891,7 +909,7 @@ Usually, an attacker exploiting a program vulnerable to stack buffer overflow is
|
||||
\label{fig:buffer_overflow_shellcode}
|
||||
\end{figure}
|
||||
|
||||
As we can observe in the figure, the attacker will take advantage of the buffer overflow to overwrite not only ret, but also the rest of the current stack frame and sfp with malicious code. This code is known as shellcode, consisting of instruction opcodes (machine assembly instructions translated to their representation in hexadecimal values) which the processor will execute. We will briefly explain how to write shellcode in section \ref{TODO probably an Annex}. Therefore, in this technique the attacker will:
|
||||
As we can observe in the figure, the attacker will take advantage of the buffer overflow to overwrite not only ret, but also the rest of the current stack frame and sfp with malicious code. This code is known as shellcode, consisting of instruction opcodes (machine assembly instructions translated to their representation in hexadecimal values) which the processor will execute. We will explain how to write shellcode in Section \ref{subsection:got_attack}. Therefore, in this technique the attacker will:
|
||||
\begin{itemize}
|
||||
\item Introduce a byte array that overflows the buffer, consisting on SHELLCODE + the address of the buffer.
|
||||
\begin{itemize}
|
||||
@@ -903,18 +921,18 @@ As we can observe in the figure, the attacker will take advantage of the buffer
|
||||
|
||||
Although the classic buffer overflow is one of the best-known techniques in binary exploitation, it is also one of the oldest and thus numerous protections have historically been incorporated to mitigate this type of exploits. This is why the attack presented here does not work work in a modern system anymore.
|
||||
|
||||
The reason is that one of the protections consists of the prohibition of executing code from the stack. By marking the stack as non-executable, in the case of rip pointing to an address in the stack any malicious code will not be run, even if an application was vulnerable to a buffer overflow. We will explain more in detail the main protections that nowadays are incorporated in modern systems in section \ref{subsection:hardening_elf}.
|
||||
The reason is that one of the protections consists of the prohibition of executing code from the stack. By marking the stack as non-executable, in the case of rip pointing to an address in the stack any malicious code will not be run, even if an application was vulnerable to a buffer overflow. We will explain more in detail the main protections that nowadays are incorporated in modern systems in Section \ref{subsection:hardening_elf}.
|
||||
|
||||
\subsection{Return oriented programming attacks} \label{subsection:rop}
|
||||
After the stack was marked non-executable, a new refined technique was invented to circumvent this restriction and adapt the classic buffer overflow to modern systems. In the end, attackers still maintained the ability to overflow the buffer in the stack of vulnerable applications, writing shellcode and overwriting ret, the only issue was that the shellcode could not be executed.
|
||||
|
||||
Return Oriented Programming (ROP) is an exploitation technique that takes advantage of the fact that, even if malicious code in the stack cannot be executed, the attacker can still redirect the flow of execution by modifying ret to any other piece of executable code. The challenge for the attacker is executing malicious code, since any available executable instructions are either at the .text section (which will correspond to the normal functioning of the program) or at shared libraries, but none are useful for malware.
|
||||
|
||||
ROP tackles this challenge by designing a method of reconstructing malicious code from parts of already-existing code, as in a 'collage'. Assembly instructions are selected from multiple places, so that, when put together and executed sequentially, they recreate the shellcode which the attacker wants to execute. These pieces of code are called ROP gadgets, and consist of a set of arbitrary instructions followed by a final \textit{ret} instruction, which triggers the function exit and pops the value of ret. These gadgets may belong to any code in the process memory, usually selected between the code of the shared libraries (see figure \ref{fig:stack}) to which the process is linked.
|
||||
ROP tackles this challenge by designing a method of reconstructing malicious code from parts of already-existing code, as in a 'collage'. Assembly instructions are selected from multiple places, so that, when put together and executed sequentially, they recreate the shellcode which the attacker wants to execute. These pieces of code are called ROP gadgets, and consist of a set of arbitrary instructions followed by a final \textit{ret} instruction, which triggers the function exit and pops the value of ret. These gadgets may belong to any code in the process memory, usually selected between the code of the shared libraries (see Figure \ref{fig:stack}) to which the process is linked.
|
||||
|
||||
Finding ROP gadgets and writing ROP-compatible payloads manually is hard, thus multiple programs exist that automatically scan the system libraries and construct provide the gadgets given the shellcode to execute \cite{rop_prog_finder}.
|
||||
|
||||
However, we will now illustrate how ROP works with an example. Suppose that an attacker has discovered a buffer overflow vulnerability, but the stack is marked as not executable. The attacker wants to execute the assembly code shown in snippet \ref{code:rop_ex}:
|
||||
However, we will now illustrate how ROP works with an example. Suppose that an attacker has discovered a buffer overflow vulnerability, but the stack is marked as not executable. The attacker wants to execute the assembly code shown in Code Snippet \ref{code:rop_ex}:
|
||||
|
||||
\begin{lstlisting}[language=C, caption={Sample program to run using ROP.}, label={code:rop_ex}]
|
||||
mov rdx, 10
|
||||
@@ -932,12 +950,12 @@ After finding the address of the ROP gadgets manually or using an automated tool
|
||||
|
||||
The steps described in the figure are the following:
|
||||
\begin{enumerate}
|
||||
\item First step shows the two gadgets located and their addresses, and the overwritten data in the stack. The function has already exited and, because ret was overwritten with the address of the first gadget, register rip now points to that location, and thus it is the next instruction to execute. Register rsp, in turn, now points to the bottom address of the current stack frame, which is right next to the old ret (see section \ref{subsection:stack} for stack frames functioning).
|
||||
\item The first instruction of the gadget is executed, popping the value from the stack (which also moves register rsp, see stack push and pop operations in section \ref{subsection:stack}). As we can observe, the value "10" was specifically put in that position by the attacker, so that, according to the instruction to execute \lstinline{mov rdx, 10} \lstinline{}, we now have loaded that data into register rdx.
|
||||
\item First step shows the two gadgets located and their addresses, and the overwritten data in the stack. The function has already exited and, because ret was overwritten with the address of the first gadget, register rip now points to that location, and thus it is the next instruction to execute. Register rsp, in turn, now points to the bottom address of the current stack frame, which is right next to the old ret (see Section \ref{subsection:stack} for stack frames functioning).
|
||||
\item The first instruction of the gadget is executed, popping the value from the stack (which also moves register rsp, see stack push and pop operations in Section \ref{subsection:stack}). As we can observe, the value "10" was specifically put in that position by the attacker, so that, according to the instruction to execute \lstinline{mov rdx, 10} \lstinline{}, we now have loaded that data into register rdx.
|
||||
\item The return instruction is executed, which pops from the stack what is supposed to be the value of the saved rip, but in turn the attacker has placed the address of the next gadget there. Now, rip has jumped to the address of the second gadget. By continuing with this process, we can chain an infinite number of gadgets.
|
||||
\item Finally, we repeated the same process as before, using a pop instruction to load a value from the stack. This illustrates that push and pop instructions, commonly used on most programs, are also possible to be using ROP.
|
||||
|
||||
After this step, the return instruction will be executed. Note that, at this point, if the attacker wants to be stealthy and avoid crashing the program (since we overwrote the original data in the stack), the original stack must be restored, together with the value of the registers before the malicious code execution. We will see an example of a technique for reconstructing the original state during our explanation of the library injection in section \ref{TODO}.
|
||||
After this step, the return instruction will be executed. Note that, at this point, if the attacker wants to be stealthy and avoid crashing the program (since we overwrote the original data in the stack), the original stack must be restored, together with the value of the registers before the malicious code execution. We will see an example of a technique for reconstructing the original state during our explanation of the library injection in Section \ref{subsection:got_attack}.
|
||||
\end{enumerate}
|
||||
|
||||
|
||||
@@ -957,8 +975,8 @@ Firstly, we will describe the data structure we will be dealing with in networki
|
||||
|
||||
As we can observe, we can distinguish five different network layers in the frame. This division is made according to the OSI model \cite{network_layers}:
|
||||
\begin{itemize}
|
||||
\item Layer 1 corresponds to the physical layer, and it is processed by the NIC hardware, even before it reaches the XDP module (see figure \ref{fig:xdp_diag}). Therefore, this layer is discarded and completely invisible to the kernel. Note that it does not only include a header, but also a trailer (a Frame Check Sequence, a redundancy check included to check frame integrity).
|
||||
\item Layer 2 is the data layer, it is in charge of transporting the frame via physical media, in our case an Ethernet connection. Most relevant fields are the MAC destination and source, used for performing physical addressing.
|
||||
\item Layer 1 corresponds to the physical layer, and it is processed by the NIC hardware, even before it reaches the XDP module (see Figure \ref{fig:xdp_diag}). Therefore, this layer is discarded and completely invisible to the kernel. Note that it does not only include a header, but also a trailer (a Frame Check Sequence, a redundancy check included to check frame integrity).
|
||||
\item Layer 2 is the data layer, it is in charge of transporting the frame via physical media, in our case an Ethernet connection. Most relevant fields are the MAC destination and source, used for physical addressing.
|
||||
\item Layer 3 is the network layer, in charge of packet forwarding and routing. In our case, packets will be using the IP protocol. Most relevant fields are the source and destination IP, used to indicate the host that sent the packet and who is the receiver.
|
||||
\item Layer 4 is the transport layer, in charge of providing end-to-end connection services to applications in a host. We will be focusing on TCP during our research. Relevant fields include the source and destination port, which indicate the ports involved in the communication on which the applications on each host are listening and sending packets.
|
||||
\item The last layer is the payload of the TCP packet, which contains, according to the OSI model, all layers belong to application data.
|
||||
@@ -968,18 +986,18 @@ As we can observe, we can distinguish five different network layers in the frame
|
||||
\subsection{Introduction to the TCP protocol} \label{subsection:tcp}
|
||||
We will now focus our view on the transport layer, specifically on the TCP protocol, since it will be a major concern at the time of designing the network capabilities of our rootkit.
|
||||
|
||||
Firstly, since TCP aims to offer a reliable and ordered packet transmission \cite{tcp_reliable}, it includes sequence numbers (see table \ref{fig:frame}) which mark the order in which they are transmitted. However, since the physical medium may corrupt or lose packets during the transmission, TCP must incorporate mechanisms for ensuring the order and delivery of all packets:
|
||||
Firstly, since TCP aims to offer a reliable and ordered packet transmission \cite{tcp_reliable}, it includes sequence numbers (see Table \ref{fig:frame}) which mark the order in which they are transmitted. However, since the physical medium may corrupt or lose packets during the transmission, TCP must incorporate mechanisms for ensuring the order and delivery of all packets:
|
||||
\begin{itemize}
|
||||
\item Mechanism for opening and establishing a reliable connection between two parties.
|
||||
\item Mechanism for ensuring that packets are retransmitted in case of an error during the connection.
|
||||
\end{itemize}
|
||||
|
||||
With respect to the establishment of a reliable connection, this is achieved via a 3-way handshake, in which certain TCP flags will be set in a series of interchanged packets (see in figure \ref{fig:frame} the field TCP flags). Most relevant TCP flags are described in table \ref{table:tcp_flags}.
|
||||
With respect to the establishment of a reliable connection, this is achieved via a 3-way handshake, in which certain TCP flags will be set in a series of interchanged packets (see in Figure \ref{fig:frame} the field TCP flags). Most relevant TCP flags are described in Table \ref{table:tcp_flags}.
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{4cm}|>{\centering\arraybackslash}p{10cm}|}
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{3cm}|>{\centering\arraybackslash}p{10cm}|}
|
||||
\hline
|
||||
Flag & Purpose\\
|
||||
\textbf{FLAG} & \textbf{PURPOSE}\\
|
||||
\hline
|
||||
\hline
|
||||
ACK & Acknowledges that a packet has been successfully received. In the acknowledgment number (see figure \ref{fig:frame}), it is stored the sequence number of the packet being acknowledged + 1. \\
|
||||
@@ -995,7 +1013,7 @@ RST & Abruptly terminates the connection, usually sent when a host receives an u
|
||||
\label{table:tcp_flags}
|
||||
\end{table}
|
||||
|
||||
Taking the above into account, figure \ref{fig:tcp_conn} shows a depiction of the 3-way handshake \cite{tcp_handshake}:
|
||||
Taking the above into account, Figure \ref{fig:tcp_conn} shows a depiction of the 3-way handshake \cite{tcp_handshake}:
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
\includegraphics[width=12cm]{tcp_conn.jpg}
|
||||
@@ -1005,7 +1023,7 @@ Taking the above into account, figure \ref{fig:tcp_conn} shows a depiction of th
|
||||
|
||||
As we can observe in the figure, the hosts interchange a sequence of SYN, SYN+ACK, ACK packets, after which the communication starts. During this communication, the sender transmits packets with data (and no flags set), to which it expects an ACK packet acknowledging having received it.
|
||||
|
||||
With respect to maintaining the integrity of the connection once it starts, TCP works using timers, as it is illustrated in figure \ref{fig:tcp_retransmission}:
|
||||
With respect to maintaining the integrity of the connection once it starts, TCP works using timers, as it is illustrated in Figure \ref{fig:tcp_retransmission}:
|
||||
\begin{enumerate}
|
||||
\item A data packet with sequence number X is sent. The timer starts.
|
||||
\item The destination host receives the packet and returns an ACK packet with acknowledgment number X+1.
|
||||
@@ -1022,7 +1040,7 @@ With respect to maintaining the integrity of the connection once it starts, TCP
|
||||
\section{ELF binaries} \label{section:elf}
|
||||
This section details the Executable and Linkable Format (ELF) \cite{elf}, the format in which we find executable files (between other file types) in Linux systems. We will perform an analysis from a security standpoint, that is, mainly oriented to describe the most relevant sections and the permissions incorporated into them. We will also focus on several of these sections which will be relevant for designing our attack.
|
||||
|
||||
After that, we will overview the security hardening techniques that have been historically incorporated into Linux to mitigate possible exploitation techniques when running ELF executables (such as the stack buffer overflow we explained in section \ref{subsection: buf_overflow}). During the design of our rootkit, we will attempt to bypass these techniques using multiple workarounds.
|
||||
After that, we will overview the security hardening techniques that have been historically incorporated into Linux to mitigate possible exploitation techniques when running ELF executables (such as the stack buffer overflow we explained in Section \ref{subsection: buf_overflow}). During the design of our rootkit, we will attempt to bypass these techniques using multiple workarounds.
|
||||
|
||||
\subsection{The ELF format and Lazy Binding} \label{subsection:elf_lazy_binding}
|
||||
Linux supports multiple tools that enable a deep inspection of ELF binaries and its sections. Table \ref{table:elf_tools} shows the main tools we will use during this analysis:
|
||||
@@ -1030,7 +1048,7 @@ Linux supports multiple tools that enable a deep inspection of ELF binaries and
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{3cm}|>{\centering\arraybackslash}p{10cm}|}
|
||||
\hline
|
||||
Tool & Purposes\\
|
||||
\textbf{TOOL} & \textbf{PURPOSE}\\
|
||||
\hline
|
||||
\hline
|
||||
Readelf & Display information about ELF files\\
|
||||
@@ -1046,14 +1064,14 @@ GDB-peda & The Python Exploit Development Assistance for GDB, allows for multipl
|
||||
\label{table:elf_tools}
|
||||
\end{table}
|
||||
|
||||
Firstly, we will analyse the main sections we can find in an ELF executable. We will approach this study using a sample program that has been compiled using Clang/LLVM: TODO %TODO How do I explain which progrm it is? It is an example I developed, src/helpers/simple_timer.c. Shoud I write the code somewhere? Seems excesive
|
||||
Firstly, we will analyse the main sections we can find in an ELF executable. We will approach this study using a sample program that has been compiled using Clang/LLVM, and that consists on a simple timer that counts twice up to number 3, available at our repository \cite{repo_simple_timer}.
|
||||
|
||||
The commands used and complete list of headers can be found in Annex \ref{annexsec:readelf_sec_headers}. The most relevant sections are described in table \ref{table:elf_sec_headers}:
|
||||
The commands used for this analysis and complete list of headers can be found in Annex \ref{annexsec:readelf_sec_headers}. The most relevant sections we found at the program are described in Table \ref{table:elf_sec_headers}:
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{1cm}|>{\centering\arraybackslash}p{9cm}|>{\centering\arraybackslash}p{2cm}|}
|
||||
\hline
|
||||
Tool & Purpose & Permissions\\
|
||||
\textbf{TOOL} & \textbf{PURPOSE} & \textbf{PERMS}\\
|
||||
\hline
|
||||
\hline
|
||||
.init & Contains instructions executed before the \textit{main} function of the program & Alloc, Executable\\
|
||||
@@ -1073,11 +1091,11 @@ Tool & Purpose & Permissions\\
|
||||
.bss & Contains global and static variables which are uninitialized or initialized to zero. & Alloc, Writable\\
|
||||
\hline
|
||||
\end{tabular}
|
||||
\caption{Tools used for analysis of ELF programs.}
|
||||
\caption{Sections in an ELF file.}
|
||||
\label{table:elf_sec_headers}
|
||||
\end{table}
|
||||
|
||||
As it can be observed in table \ref{table:elf_sec_headers}, we can find that all sections have the Alloc flag, meaning they will be loaded into process memory during runtime (see table \ref{TODO}, they have not been shown in previous diagrams for simpleness).
|
||||
As it can be observed in Table \ref{table:elf_sec_headers}, we can find that all sections have the Alloc flag, meaning they will be loaded into process memory during runtime.
|
||||
|
||||
Apart from those we have already discussed previously, we can find the GOT and PLT sections, whose purpose is to support Position Independent Code (PIC), that is, instructions whose address in virtual memory is not hardcoded by the compiler into the executable, but rather they are not known until resolved at runtime. This is usually the case of shared libraries, which can be loaded into virtual memory starting at any address \cite{plt_got_overlord}.
|
||||
|
||||
@@ -1108,7 +1126,7 @@ $ objdump -d simple_timer
|
||||
\label{fig:lazy_bind_3}
|
||||
\end{figure}
|
||||
|
||||
\item As we can see in figures \ref{fig:lazy_bind_2} and \ref{fig:lazy_bind_3}, the PLT stub calls address 0x4010a0, which leads to a dynamic linking routine, which proceeds to write the address into the GOT section and jump back to the start of the PLT stub. This time, the memory address at GOT to which the PLT jumps is already loaded with the address to the function at the shared library, as shown by figure \ref{fig:lazy_bind_4}.
|
||||
\item As we can see in Figures \ref{fig:lazy_bind_2} and \ref{fig:lazy_bind_3}, the PLT stub calls address 0x4010a0, which leads to a dynamic linking routine, which proceeds to write the address into the GOT section and jump back to the start of the PLT stub. This time, the memory address at GOT to which the PLT jumps is already loaded with the address to the function at the shared library, as shown by Figure \ref{fig:lazy_bind_4}.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -1128,17 +1146,17 @@ $ objdump -d simple_timer
|
||||
|
||||
Therefore, in essence, when using lazy binding the dynamic linker will individually load into GOT the addresses of the functions at the shared libraries, during the first time they are called in the program. After that, the address will remain in the GOT section and will be used by the PLT for all subsequent calls.
|
||||
|
||||
The reason lazy binding matters to us is because, as we will explain section \ref{subsection:got_attack}, the GOT section is actually writable from an eBPF program. This is because this section specifically must be writeable at runtime for the dynamic linker to store the address once they are resolved. Therefore, we would be able to modify the GOT section from eBPF, redirecting the address at which the PLT jumps, and thus controlling the flow of execution in the program.
|
||||
The reason lazy binding matters to us is because, as we will explain Section \ref{subsection:got_attack}, the GOT section is actually writable from an eBPF program. This is because this section specifically must be writeable at runtime for the dynamic linker to store the address once they are resolved. Therefore, we would be able to modify the GOT section from eBPF, redirecting the address at which the PLT jumps, and thus controlling the flow of execution in the program.
|
||||
|
||||
\subsection{Hardening ELF binaries} \label{subsection:hardening_elf}
|
||||
During section \ref{section:attacks_stack}, we presented multiple of the classic attacks at the stack such as buffer overflow and ROP. However, as we mentioned, during the years multiple hardening measures have been introduced into modern compilers, which attempt to mitigate these and other techniques. We will now present them so that, during the design of our rootkit, we can attempt to bypass all of these.
|
||||
During Section \ref{section:attacks_stack}, we presented multiple of the classic attacks at the stack such as buffer overflow and ROP. However, as we mentioned, during the years multiple hardening measures have been introduced into modern compilers, which attempt to mitigate these and other techniques. We will now present them so that, during the design of our rootkit, we can attempt to bypass all of these.
|
||||
|
||||
Table \ref{table:compilers} shows the compilers that we will be considering during this study. We will be exclusively looking at those security features that are included by default.
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{5cm}|>{\centering\arraybackslash}p{9cm}|}
|
||||
\hline
|
||||
Compiler & Security features by default\\
|
||||
\textbf{COMPILER} & \textbf{SECURITY FEATURES BY DEFAULT}\\
|
||||
\hline
|
||||
\hline
|
||||
Clang/LLVM 12.0.0 (2021) & Stack canaries, DEP/NX, ASLR\\
|
||||
@@ -1156,7 +1174,7 @@ Stack canaries are random data that is pushed into the stack before calling pote
|
||||
If a stack canary is present and a buffer overflow happened, it would potentially overwrite the value of the canary, therefore alerting of the attack, in which case the processor halts the execution of the program.
|
||||
|
||||
\textbf{DEP/NX}\\
|
||||
Data Execution Prevention, also known as No Execute, is the option of marking the stack as non-executable. This prevents, as we explained in section \ref{subsection: buf_overflow}, the possibility of executing injected shellcode in the stack after modifying the value of the saved rip.
|
||||
Data Execution Prevention, also known as No Execute, is the option of marking the stack as non-executable. This prevents, as we explained in Section \ref{subsection: buf_overflow}, the possibility of executing injected shellcode in the stack after modifying the value of the saved rip.
|
||||
|
||||
The creation of advanced techniques like ROP is one reaction to this mitigation, that circumvents this protection.
|
||||
|
||||
@@ -1169,7 +1187,7 @@ In the context of a stack buffer overflow attack, the memory position of the sta
|
||||
Position Independent Executable is a mitigation introduced to reduce the ability of an attacker to locate symbols in virtual memory by randomizing the base address at which the program itself (including the .text section) is loaded. This base address determines an offset which is added to all memory addresses in the code, so that each instruction is located at an address + this offset. Therefore, all jumps are made using relative addresses \cite{aslr_pie_intro}.
|
||||
|
||||
\textbf{RELRO}\\
|
||||
Relocation Read-Only is a hardening technique that mitigates the possibility of an attacker overwriting the GOT section, as we explained at section \ref{subsection:elf_lazy_binding}. In order to achieve the lazy binding process is substituted by the linker resolving all entries in the GOT section right after the beginning of the execution, and then marking the .got section as read-only.
|
||||
Relocation Read-Only is a hardening technique that mitigates the possibility of an attacker overwriting the GOT section, as we explained at Section \ref{subsection:elf_lazy_binding}. In order to achieve the lazy binding process is substituted by the linker resolving all entries in the GOT section right after the beginning of the execution, and then marking the .got section as read-only.
|
||||
|
||||
Two settings for RELRO are the most widespread, either Partial RELRO (which only marks sections of the .got section not related to the PLT as read-only, leaving .got.plt writeable) or Full RELRO (which marks the .got section as read-only completely). Binaries with only Partial RELRO are still non-secure, as the address at which the PLT section jumps can still be overwriten (including from eBPF, as we will explain) \cite{relro_redhat}.
|
||||
|
||||
@@ -1192,7 +1210,7 @@ Note that the access control for the \textit{/proc/<pid>/} is governed by the va
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{3cm}|>{\centering\arraybackslash}p{11cm}|}
|
||||
\hline
|
||||
Value & Description\\
|
||||
\textbf{VALUE} & \textbf{DESCRIPTION}\\
|
||||
\hline
|
||||
\hline
|
||||
0 & Unprivileged processes may access any file or subdirectory\\
|
||||
@@ -1209,7 +1227,7 @@ Value & Description\\
|
||||
In Ubuntu 21.04, the value of this setting is of '1', therefore the access is limited to users with root privileges or to unprivileged users accessing only their own or their children process information.
|
||||
|
||||
\subsection{/proc/<pid>/maps} \label{subsection:proc_maps}
|
||||
This file provides, for the process with process ID <pid>, its mapped memory regions and their access permissions, that is, those virtual memory pages actively connected to a physical memory page (as shown in figure \ref{fig:mem_arch_pages}).
|
||||
This file provides, for the process with process ID <pid>, its mapped memory regions and their access permissions, that is, those virtual memory pages actively connected to a physical memory page (as shown in Figure \ref{fig:mem_arch_pages}).
|
||||
|
||||
Figure \ref{fig:proc_maps_sample} shows the maps file of a simple program. As we can observe, by reading this file we can get information such as:
|
||||
\begin{itemize}
|
||||
@@ -1218,13 +1236,13 @@ Figure \ref{fig:proc_maps_sample} shows the maps file of a simple program. As we
|
||||
\item In the case of memory from a file, the offset from which the data was loaded.
|
||||
\item A pathname, in the case that memory section was loaded from a file.
|
||||
|
||||
The ability to easily find memory sections on the virtual address space of a process with a specific set of permissions is particularly relevant for this research. Also, apart from disclosing the address of the stack (and sometimes the heap too), we can infer the address of other memory sections such as the .text section, which must be the only one marked as executable (in figure \ref{fig:proc_maps_sample}, the second entry that appears).
|
||||
The ability to easily find memory sections on the virtual address space of a process with a specific set of permissions is particularly relevant for this research. Also, apart from disclosing the address of the stack (and sometimes the heap too), we can infer the address of other memory sections such as the .text section, which must be the only one marked as executable (in Figure \ref{fig:proc_maps_sample}, the second entry that appears).
|
||||
|
||||
\end{itemize}
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
\includegraphics[width=15.5cm]{sch_proc_maps_sample.png}
|
||||
\includegraphics[width=15cm]{sch_proc_maps_sample.png}
|
||||
\caption{File /proc/<pid>/maps of a sample program.}
|
||||
\label{fig:proc_maps_sample}
|
||||
\end{figure}
|
||||
|
||||
@@ -3,23 +3,24 @@ In the previous chapter, we detailed which functionalities eBPF offers and studi
|
||||
|
||||
Therefore, given the previous background, this chapter is dedicated to an analysis in detail of the security implications of a malicious use of eBPF. For this, we will firstly explore the security features incorporated in the eBPF system. Then, we will identify the fundamental pillars onto which malware can build their functionality. As we mentioned during the project goals, these main topics of research will be the following:
|
||||
\begin{itemize}
|
||||
\item Analysing eBPF's possibilities when hooking system calls and kernel functions.
|
||||
\item Learning eBPF's potential to read/write arbitrary memory.
|
||||
\item Exploring networking capabilities with eBPF packet filters.
|
||||
\item Analyze eBPF's possibilities to hook system calls and kernel
|
||||
functions.
|
||||
\item Explore eBPF's potential to read/write arbitrary memory.
|
||||
\item Research networking capabilities with eBPF packet filters.
|
||||
\end{itemize}
|
||||
|
||||
|
||||
\section{eBPF maps security}
|
||||
In section \ref{subsection:access_control}, we explained that only programs with CAP\_SYS\_ADMIN are allowed to iterate over eBPF maps. The reason why this is restricted to privileged programs is because it is functionality that is a potential security vulnerability, which we will now proceed to analyse.
|
||||
In Section \ref{subsection:access_control}, we explained that only programs with CAP\_SYS\_ADMIN are allowed to iterate over eBPF maps. The reason why this is restricted to privileged programs is because it is functionality that is a potential security vulnerability, which we will now proceed to analyse.
|
||||
|
||||
Also, in section \ref{subsection:ebpf_maps}, we mentioned that eBPF maps are opened by specifying an ID (which works similarly to the typical file descriptors), while in table \ref{table:ebpf_map_types} we showed that, for performing operations over eBPF maps using the bpf() syscall, the map ID must be specified too.
|
||||
Also, in Section \ref{subsection:ebpf_maps}, we mentioned that eBPF maps are opened by specifying an ID (which works similarly to the typical file descriptors), while in Table \ref{table:ebpf_map_types} we showed that, for performing operations over eBPF maps using the bpf() syscall, the map ID must be specified too.
|
||||
|
||||
Map IDs are known by a program after creating the eBPF map, however, a program can also explore all the available maps in the system by using the BPF\_MAP\_GET\_NEXT\_ID operation in the bpf() syscall, which allows for iterating through a complete hidden list of all the maps created. This means that privileged programs can find and have read and write access to any eBPF map used by any program in the system.
|
||||
|
||||
Therefore, a malicious privileged eBPF program can access and modify other programs' maps, which can lead to:
|
||||
\begin{itemize}
|
||||
\item Modify data used for the program operation. This is the case for maps which mainly store data structures, such as BPF\_MAP\_TYPE\_HASH.
|
||||
\item Modify the program control flow, altering the instructions executed by an eBPF program. This can be achieved if a program is using the bpf\_tail\_call() helper (introduced in table \ref{table:ebpf_helpers}) which is taking data from a map storing eBPF programs (BPF\_MAP\_TYPE\_PROG\_ARRAY, introduced in table \ref{table:ebpf_map_types}).
|
||||
\item Modify the program control flow, altering the instructions executed by an eBPF program. This can be achieved if a program is using the bpf\_tail\_call() helper (introduced in table \ref{table:ebpf_helpers}) which is taking data from a map storing eBPF programs (BPF\_MAP\_TYPE\_PROG\_ARRAY, introduced in Table \ref{table:ebpf_map_types}).
|
||||
\end{itemize}
|
||||
|
||||
|
||||
@@ -27,7 +28,7 @@ Therefore, a malicious privileged eBPF program can access and modify other progr
|
||||
eBPF tracing programs (kprobes, uprobes and tracepoints) are hooked to specific points in the kernel or in the user space, and call probe functions once the flow of execution reaches the instruction to which they are attached. This section details the main security concerns regarding this type of programs.
|
||||
|
||||
\subsection{Access to function arguments} \label{subsection:tracing_arguments}
|
||||
As we saw in section \ref{section:ebpf_prog_types}, tracing programs receive as a parameter those arguments with which the hooked function originally was called. These parameters are read-only and thus, in principle, they cannot be modified inside the tracing program (we will show this is not entirely true in section \ref{section:mem_corruption}). The next code snippets show the format in which parameters are received when using libbpf (Note that libbpf also includes some macros that offer an alternative format, but the parameters are the same).
|
||||
As we saw in Section \ref{section:ebpf_prog_types}, tracing programs receive as a parameter those arguments with which the hooked function originally was called. These parameters are read-only and thus, in principle, they cannot be modified inside the tracing program (we will show this is not entirely true in Section \ref{section:mem_corruption}). The next code snippets show the format in which parameters are received when using libbpf (Note that libbpf also includes some macros that offer an alternative format, but the parameters are the same).
|
||||
|
||||
\begin{lstlisting}[language=C, caption={Probe function for a kprobe on the kernel function vfs\_write.}, label={code:format_kprobe}]
|
||||
SEC("kprobe/vfs_write")
|
||||
@@ -44,7 +45,7 @@ SEC("tp/syscalls/sys_enter_read")
|
||||
int tp_sys_enter_read(struct sys_read_enter_ctx *ctx) {
|
||||
\end{lstlisting}
|
||||
|
||||
In code snippets \ref{code:format_kprobe} and \ref{code:format_uprobe} we can identify that the parameters are passed to kprobe and uprobe programs as a pointer to a \textit{struct pt\_regs*}. This struct contains as many attributes as registers exist in the system architecture, in our case x86\_64. Therefore, on each probe function, we will receive the state of the registers at the original hooked function. This explains the format of the \textit{struct pt\_regs}, shown in code snippet \ref{code:format_ptregs}:
|
||||
In Code snippets \ref{code:format_kprobe} and \ref{code:format_uprobe} we can identify that the parameters are passed to kprobe and uprobe programs as a pointer to a \textit{struct pt\_regs*}. This struct contains as many attributes as registers exist in the system architecture, in our case x86\_64. Therefore, on each probe function, we will receive the state of the registers at the original hooked function. This explains the format of the \textit{struct pt\_regs}, shown in Code snippet \ref{code:format_ptregs}:
|
||||
|
||||
\begin{lstlisting}[language=C, caption={Format of struct pt\_regs.}, label={code:format_ptregs}]
|
||||
struct pt_regs {
|
||||
@@ -79,7 +80,7 @@ By observing the value of the registers, we can extract the parameters of the or
|
||||
\hline
|
||||
\multicolumn{2}{|c|}{User interface}\\
|
||||
\hline
|
||||
Register & Purpose\\
|
||||
\textbf{REGISTER} & \textbf{PURPOSE}\\
|
||||
\hline
|
||||
\hline
|
||||
rdi & 1st argument\\
|
||||
@@ -124,7 +125,7 @@ rax & Return value\\
|
||||
\label{table:systemv_abi}
|
||||
\end{table}
|
||||
|
||||
In the case of tracepoints, we can see in code snippet \ref{code:format_tracepoint} that it receives a \textit{struct sys\_read\_enter\_ctx*}. This struct must be manually defined, as explained in \ref{subsection:tracepoints}, by looking at the file \textit{/sys/kernel/debug/tracing/events/syscalls/sys\_enter\_read/format}. Code snippet \ref{code:sys_enter_read_tp} shows the format of the struct.
|
||||
In the case of tracepoints, we can see in Code snippet \ref{code:format_tracepoint} that it receives a \textit{struct sys\_read\_enter\_ctx*}. This struct must be manually defined, as explained in Section \ref{subsection:tracepoints}, by looking at the file \textit{/sys/kernel/debug/tracing/events/syscalls/sys\_enter\_read/format}. Code snippet \ref{code:sys_enter_read_tp} shows the format of the struct.
|
||||
|
||||
\begin{lstlisting}[language=C, caption={Format for parameters in sys\_enter\_read specified at the format file.}, label={code:sys_enter_read_tp_format}]
|
||||
field:unsigned short common_type; offset:0; size:2; signed:0;
|
||||
@@ -150,9 +151,9 @@ struct sys_read_enter_ctx {
|
||||
|
||||
As we can observe, we are given a set of attributes which include the parameters with which the syscall was called. Moreover, we can still obtain an address pointing to another \textit{struct pt\_regs}, as in kprobes and uprobes, by combining the first four fields and considering it as a 32-bit long address. This means we will still be able to extract the value of the rest of the registers too.
|
||||
|
||||
It must be noted that, in syscalls, in addition to use the kernel parameter passing convention specified in table \ref{table:systemv_abi}, the number specifying the syscall must be passed in register rax too.
|
||||
It must be noted that, in syscalls, in addition to use the kernel parameter passing convention specified in Table \ref{table:systemv_abi}, the number specifying the syscall must be passed in register rax too.
|
||||
|
||||
On a final note, as we mentioned in section \ref{section:ebpf_prog_types}, there exist differences in the parameters received in probe functions depending on the two variations of tracing programs. Therefore:
|
||||
On a final note, as we mentioned in Section \ref{section:ebpf_prog_types}, there exist differences in the parameters received in probe functions depending on the two variations of tracing programs. Therefore:
|
||||
\begin{itemize}
|
||||
\item kprobe, uprobe and \textit{enter} tracepoints will receive the full parameters as we specified before, but not the return value of the function (since it is not executed yet).
|
||||
\item kretprobes, uretprobes and \textit{exit} tracepoints will still receive the \textit{struct pt\_regs}, but without any of the parameters and with only the return value of the function.
|
||||
@@ -170,19 +171,19 @@ Usually, since many function arguments are pointers to user or kernel addresses
|
||||
\item bpf\_probe\_read\_kernel()
|
||||
\end{itemize}
|
||||
|
||||
These helpers, previously introduced in table \ref{table:ebpf_helpers}, enable to read an arbitrary number of bytes from an user or kernel address respectively, allowing us to extract the information pointed by the parameters received by eBPF programs.
|
||||
These helpers, previously introduced in Table \ref{table:ebpf_helpers}, enable to read an arbitrary number of bytes from an user or kernel address respectively, allowing us to extract the information pointed by the parameters received by eBPF programs.
|
||||
|
||||
\subsection{Reading memory out of bounds} \label{subsection:out_read_bounds}
|
||||
As we introduced in the previous subsection, the bpf\_probe\_read\_user() and bpf\_probe\_read\_kernel() helpers can be used to access memory of pointers received as parameters in the hooked functions.
|
||||
As we introduced in the previous section, the bpf\_probe\_read\_user() and bpf\_probe\_read\_kernel() helpers can be used to access memory of pointers received as parameters in the hooked functions.
|
||||
|
||||
However, although in general the eBPF verifier attempts to reject illegal memory accesses, it does not prevent a malicious program from passing an arbitrary memory address (in kernel or user space) to the above helpers. This means that an eBPF program can potentially read any address in user or kernel space, (as long as it is marked as readable in the corresponding memory pages). Furthermore, an attacker can locate specific data structures and memory sections by taking the function parameter as a reference point in memory.
|
||||
|
||||
A particularly relevant case (which we will later use for our rootkit) involves accessing user memory via the parameters of tracepoints attached at system calls. Provided the nature of syscalls, whose purpose is to communicate user and kernel space, all parameters received will belong to the user space, and therefore any pointer passed will be an address in user memory. This enables an eBPF program to get a foothold into the virtual address space of the process calling the syscall, which it can proceed to scan looking for data or specific instructions. This technique will be further elaborated in section \ref{subsection_bpf_probe_write_apps}.
|
||||
A particularly relevant case (which we will later use for our rootkit) involves accessing user memory via the parameters of tracepoints attached at system calls. Provided the nature of syscalls, whose purpose is to communicate user and kernel space, all parameters received will belong to the user space, and therefore any pointer passed will be an address in user memory. This enables an eBPF program to get a foothold into the virtual address space of the process calling the syscall, which it can proceed to scan looking for data or specific instructions. This technique will be further elaborated in Section \ref{subsection:bpf_probe_write_apps}.
|
||||
|
||||
\subsection{Overriding function return values}
|
||||
A potentially dangerous functionality in eBPF tracing programs is the ability to modify the return value of kernel functions\cite{ebpf_friends_p15}\cite{ebpf_override_return}. This can be done via the eBPF helper bpf\_override\_return, and it works exclusively from kretprobes.
|
||||
|
||||
Apart from only working on kretprobes, additional restrictions are applied to this helper. It will only work if the kernel was compiled with the CONFIG\_BPF\_KPROBE\_OVERRIDE flag, and only if the kretprobe is attached to a function to which, during the kernel development, the macro ALLOW\_ERROR\_INJECTION() has been indicated. Currently, only a small selection of functions includes this macro, but most system calls can be found to implement it. The following code snippets show how a system call like sys\_open is defined in kernel v5.11:
|
||||
Apart from only working on kretprobes, additional restrictions are applied to this helper. It will only work if the kernel was compiled with the CONFIG\_BPF\_KPROBE\_OVERRIDE flag, and only if the kretprobe is attached to a function to which, during the kernel development, the macro ALLOW\_ERROR\_INJECTION() has been indicated. Currently, only a small selection of functions includes this macro, but most system calls can be found to implement it. Code snippets \ref{code:override_return_1} and \ref{code:override_return_2} show how a system call like sys\_open is defined in kernel v5.11:
|
||||
|
||||
\begin{lstlisting}[language=C, caption={Definition of the syscall sys\_open in the kernel \cite{code_kernel_open}}, label={code:override_return_1}]
|
||||
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
|
||||
@@ -203,7 +204,7 @@ SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
|
||||
\end{lstlisting}
|
||||
|
||||
|
||||
By looking at snippets \ref{code:override_return_1} and \ref{code:override_return_2}, we can observe that the system call sys\_open involves the inclusion of the ALLOW\_ERROR\_INJECTION macro. Therefore, any kretprobe attached to a system call function will be able to modify its return value.
|
||||
By looking at Code snippets \ref{code:override_return_1} and \ref{code:override_return_2}, we can observe that the system call sys\_open involves the inclusion of the ALLOW\_ERROR\_INJECTION macro. Therefore, any kretprobe attached to a system call function will be able to modify its return value.
|
||||
|
||||
In order to be able to modify the return value of functions, the aforementioned eBPF helper makes use of the fault injection framework of the Linux kernel\cite{fault_injection}, which was created before eBPF itself, and whose original purpose is to allow for generating errors in kernel programs for debugging purposes.
|
||||
|
||||
@@ -224,7 +225,7 @@ As a summary, a malicious eBPF program loaded and attached as a tracing program
|
||||
Its ability to access sensitive data in function parameters and reading arbitrary memory can lead to gathering extensive information on the running processes of a system, whilst the malicious use of eBPF helpers enables the modification of the data passed to the user space from the kernel, and the control over which programs are allowed to be running on the system.
|
||||
|
||||
\section{Memory corruption} \label{section:mem_corruption}
|
||||
In the previous section we described how tracing programs can read user memory out of the bounds of function parameters via the helpers bpf\_probe\_read\_user() and bpf\_probe\_read\_kernel(). In this section, we will analyse another eBPF helper can be found to be the heart of malicious programs.
|
||||
In the previous section we described how tracing programs can read user memory out of the bounds of function parameters via the helpers bpf\_probe\_read\_user() and bpf\_probe\_read\_kernel(). In this section, we will analyse another eBPF helper that can be found to be the heart of malicious programs.
|
||||
|
||||
Privileged eBPF programs (or those with at least CAP\_BPF + CAP\_PERFMON capabilities) have the potential to use an experimental (it is labelled as so \cite{ebpf_helpers}) helper called bpf\_probe\_write\_user(). This helper enables to write into user memory from within an eBPF program.
|
||||
|
||||
@@ -237,21 +238,21 @@ Provided the background into memory architecture and the stack operation, we wil
|
||||
The bpf\_probe\_write\_user() helper, when used from a tracing eBPF program, can write into any memory address in the user space of the process responsible from calling the hooked function. However, the write operation fails has some restrictions:
|
||||
\begin{itemize}
|
||||
\item{The operation fails if the memory space pointed by the address is marked as non-writeable by the user space process. For instance, if we try to write into the .text section, the helpers fails because this section is only marked as readable and executable (for protection reasons).} Therefore, the process must indicate a writeable flag in the memory section for the helper to succeed.
|
||||
\item{The operation fails if the memory page is served with a minor or major page fault \cite{bpf_probe_write_user_errors}. As we saw in section \ref{subsection:ebpf_verifier}, eBPF programs are restricted from executing any sleeping or blocking operations, to prevent hanging the kernel. Therefore, since during a page fault the operating system needs to block the execution and write into the page table or retrieve data from the secondary disk, bpf\_probe\_write\_user() is defined as a non-faulting helper\cite{write_helper_non_fault}, meaning that instead of issuing a page fault for accessing data, it will just return and fail.}
|
||||
\item{Each time the helper is called, an alert message is written into the kernel logs, alerting that a potentially dangerous eBPF program is making use of the helper. Note that this message appears when the eBPF program is attached, and not each time the helper is called. This will be particularly relevant since we will be able to bypass this alert by taking advantage of this.}
|
||||
\item{The operation fails if the memory page is served with a minor or major page fault \cite{bpf_probe_write_user_errors}. As we saw in Section \ref{subsection:ebpf_verifier}, eBPF programs are restricted from executing any sleeping or blocking operations, to prevent hanging the kernel. Therefore, since during a page fault the operating system needs to block the execution and write into the page table or retrieve data from the secondary disk, bpf\_probe\_write\_user() is defined as a non-faulting helper\cite{write_helper_non_fault}, meaning that instead of issuing a page fault for accessing data, it will just return and fail.}
|
||||
\item{Each time the helper is called, an alert message is written into the kernel logs, alerting that a potentially dangerous eBPF program is making use of the helper. Note that this message appears when the eBPF program is attached, and not each time the helper is called. This is particularly relevant since a malicious eBPF can bypass this alert by blocking read calls during the attachment stage, later overwriting the kernel logs once the eBPF programs using this helper have been attached. After that, since it is already attached, the eBPF program may use the helper without any warning message \cite{ebpf_friends_54}.}
|
||||
\end{itemize}
|
||||
|
||||
Although we will not be able to modify kernel memory or the instructions of a program, this eBPF helper opens a range of possible attacks:
|
||||
\begin{itemize}
|
||||
\item Modify any of the arguments with which a system call is called (either with a tracepoint or a kprobe). Therefore, a malicious program can hijack any call to the kernel with its own arguments.
|
||||
\item Modify user-provided arguments in kernel functions. When reading kernel code, we can find that data provided by the user is marked with the keyword \textit{\_\_user}. For instance, an internal kernel function in a nested call of the system call sys\_read receives an user buffer:
|
||||
\begin{lstlisting}[language=C, caption={Definition of kernel function vfs\_read. \cite{code_vfs_read}}, label={code:vfs_read}]
|
||||
\item Modify user-provided arguments in kernel functions. When reading kernel code, we can find that data provided by the user is marked with the keyword \textit{\_\_user}. For instance, an internal kernel function vfs\_read used by the system call sys\_read receives the user buffer shown in Code snippet \ref{code:vfs_read}.
|
||||
\begin{lstlisting}[language=C, caption={Definition of kernel function vfs\_read \cite{code_vfs_read}.}, label={code:vfs_read}]
|
||||
ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
|
||||
\end{lstlisting}
|
||||
Then, if we attach a kprobe to vfs\_read, we would be able to modify the value of the buffer.
|
||||
\item Modify process memory by taking function parameters as a reference and scanning the stack. This technique, first introduced in section \ref{subsection:out_read_bounds} when we mentioned that tracing programs can read any user memory location with the bpf\_probe\_read\_user() helper, and which was publicly first used by Jeff Dileo at his talk in DEFCON 27\cite{evil_ebpf_p6974}, consists of:
|
||||
\item Modify process memory by taking function parameters as a reference and scanning the stack. This technique, first introduced in Section \ref{subsection:out_read_bounds} when we mentioned that tracing programs can read any user memory location with the bpf\_probe\_read\_user() helper, and which was publicly first used by Jeff Dileo at his talk in DEFCON 27 \cite{evil_ebpf_p6974}, consists of:
|
||||
\begin{enumerate}
|
||||
\item Take an user-passed parameter received on a tracing program. The parameter must be a pointer to a memory location (such as a pointer to a buffer), so that we can use that memory address as the reference point in user space. According to the x86\_64 documentation, this parameter will be stored in the stack\cite{8664_params_abi_p1922}, so we will receive a stack address.
|
||||
\item Take an user-passed parameter received on a tracing program. The parameter must be a pointer to a memory location (such as a pointer to a buffer), so that we can use that memory address as the reference point in user space. According to the x86\_64 documentation, this parameter will be stored in the stack \cite{8664_params_abi_p1922}, so we will receive a stack address.
|
||||
\item Locate the target data which we aim to write. There are two main methods for this:
|
||||
\begin{itemize}
|
||||
\item Sequentially read the stack, using bpf\_probe\_read\_user(), until we locate the bytes we are looking for. This requires knowing which data we want to overwrite.
|
||||
@@ -262,7 +263,6 @@ Then, if we attach a kprobe to vfs\_read, we would be able to modify the value o
|
||||
\end{itemize}
|
||||
|
||||
Figure \ref{fig:stack_scan_write_tech} illustrates a high-level overview of the stack scanning technique previously described.
|
||||
%TODO i just noticed I included SFP outside the current stack frame, correct it here and everywhere
|
||||
\begin{figure}[H]
|
||||
\centering
|
||||
\includegraphics[width=16cm]{stack_scan_write_tech.jpg}
|
||||
@@ -270,7 +270,7 @@ Figure \ref{fig:stack_scan_write_tech} illustrates a high-level overview of the
|
||||
\label{fig:stack_scan_write_tech}
|
||||
\end{figure}
|
||||
|
||||
The figure shows process memory executing a program similar to the following:
|
||||
The figure shows process memory executing a program similar to the one shown in Code snippet \ref{code:stack_scan_write_tech}:
|
||||
\begin{lstlisting}[language=C, caption={Sample program being executed on figure \ref{fig:stack_scan_write_tech}.}, label={code:stack_scan_write_tech}]
|
||||
void func(char* a, char* b, char* c){
|
||||
int fd = open("FILE", 0);
|
||||
@@ -285,10 +285,10 @@ int main(){
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
In the figure, we can clearly observe how the technique is used to overwrite a specific buffer. The attacker goal is to overwrite buffer \textit{c} with some other bytes, but the kprobe program only has direct access to buffer \textit{a}:
|
||||
In Figure \ref{fig:stack_scan_write_tech}, we can clearly observe how the technique is used to overwrite a specific buffer. The attacker goal is to overwrite buffer \textit{c} with some other bytes, but the kprobe program only has direct access to buffer \textit{a}:
|
||||
\begin{enumerate}
|
||||
\item By reverse engineering the program (we will see how this process works in section \ref{TODO}) we notice that buffer \textit{c} is stored 8 bytes lower on the stack than buffer \textit{a}.
|
||||
\item When register rip points to the write() instruction, the processor executes the instruction and a system call is issued to sys\_write().
|
||||
\item By reverse engineering the program (e.g.: with gdb-peda) we notice that buffer \textit{c} is stored 8 bytes lower on the stack than buffer \textit{a}.
|
||||
\item When register rip points to the write() instruction, the processor executes the instruction and a system call is issued to sys\_write.
|
||||
\item The kprobe eBPF program hooked to the syscall hijacks the program execution. Since it has access to the memory address of buffer \textit{a} and it knows the relative position of buffer \textit{c}, it writes to that location whatever it wants (e.g.: "DDD") with the bpf\_probe\_write\_user() helper.
|
||||
\item The eBPF program ends and the control flow goes back to the system call. It ends its execution successfully and returns a value to the user space. The result of the program is that 1 byte has been written into file "FILE", and that buffer \textit{c} now contains "DDD".
|
||||
\end{enumerate}
|
||||
@@ -296,25 +296,25 @@ In the figure, we can clearly observe how the technique is used to overwrite a s
|
||||
\subsection{Takeaways}
|
||||
As a summary, the bpf\_probe\_write\_user() helper is one of the main attack vectors for malicious eBPF programs. Although it does contain some restrictions, its ability to overwrite any user parameter enables it to, in practice, execute arbitrary code by hijacking that of others. When it is combined with tracing programs' ability to read memory out of bounds, it unlocks a wide range of attacks, since any writeable section of the process memory is a possible target.
|
||||
|
||||
Therefore, if on the conclusion of section \ref{subsection:tracing_attacks_conclusion} we discussed that the ability to change the return value of kernel functions and kill processes hinders the trust between the user and kernel space (since what the kernel returns may not be a correct result), then the ability to directly overwrite process data is a complete disrupt of trust in any of the data in the user space itself, since it is subject to the control of a malicious eBPF program.
|
||||
Therefore, if on the conclusion of Section \ref{subsection:tracing_attacks_conclusion} we discussed that the ability to change the return value of kernel functions and kill processes hinders the trust between the user and kernel space (since what the kernel returns may not be a correct result), then the ability to directly overwrite process data is a complete disrupt of trust in any of the data in the user space itself, since it is subject to the control of a malicious eBPF program.
|
||||
|
||||
Moreover, in the next sections we will discuss how we can create advanced attacks based on the background and techniques previously discussed. We will research further into which sections of a process memory are writeable and whether they can lead to new attack vectors.
|
||||
|
||||
|
||||
\section{Abusing networking programs}\label{section:abusing_networking}
|
||||
The final main piece of a malicious eBPF program comes from taking advantage of the networking capabilities of TC and XDP programs. As we mentioned during sections \ref{subsection:xdp} and \ref{subsection:tc}, these type of programs have access to network traffic:
|
||||
The final main piece of a malicious eBPF program comes from taking advantage of the networking capabilities of TC and XDP programs. As we mentioned during Section \ref{subsection:xdp} and \ref{subsection:tc}, these type of programs have access to network traffic:
|
||||
\begin{itemize}
|
||||
\item Traffic Control programs can be placed either on egress or ingress traffic, and receive a struct \textit{sk\_buff}, containing the packet bytes and meta data that helps operating on it.
|
||||
\item External Data Path programs can only be attached to ingress traffic, but in turn they receive the packet before any kernel processing (as a struct \textit{xdp\_md}) being able to access the raw data directly.
|
||||
\item Express Data Path programs can only be attached to ingress traffic, but in turn they receive the packet before any kernel processing (as a struct \textit{xdp\_md}) being able to access the raw data directly.
|
||||
\end{itemize}
|
||||
|
||||
Networking eBPF programs not only have read access to the network packets, but also write access:
|
||||
\begin{itemize}
|
||||
\item XDP programs can directly modify the raw packet via memcpy() operations. They can also increment or reduce the size of the packet at any of its ends (adding bytes before the head or after the packet tail). This is done via the multiple helpers previously presented on table \ref{table:xdp_helpers}.
|
||||
\item TC programs can also modify the packet via the helpers presented on table \ref{table:tc_helpers}. The packet can be expanded or reduced via these eBPF helpers too.
|
||||
\item XDP programs can directly modify the raw packet via memcpy() operations. They can also increment or reduce the size of the packet at any of its ends (adding bytes before the head or after the packet tail). This is done via the multiple helpers previously presented on Table \ref{table:xdp_helpers}.
|
||||
\item TC programs can also modify the packet via the helpers presented on Table \ref{table:tc_helpers}. The packet can be expanded or reduced via these eBPF helpers too.
|
||||
\end{itemize}
|
||||
|
||||
Apart from write access to the packet, the other critical feature of networking programs is their ability to drop packets. As we presented in tables \ref{table:xdp_actions_av} and \ref{table:tc_actions}, this can be achieved by returning specific values.
|
||||
Apart from write access to the packet, the other critical feature of networking programs is their ability to drop packets. As we presented in Table \ref{table:xdp_actions_av} and \ref{table:tc_actions}, this can be achieved by returning specific values.
|
||||
|
||||
|
||||
\subsection{Attacks and limitations of networking programs} \label{subsection:network_attacks}
|
||||
@@ -322,7 +322,7 @@ Based on the previous background, we will now proceed to explore which limitatio
|
||||
\begin{itemize}
|
||||
\item Read and write access to the packet is heavily controlled by the eBPF verifier. It is not possible to read or write data out of bounds. Extreme care must also be taken before attempting to read any data inside the packet, since the verifier first requires making lots of checks beforehand. For any access to take place, the program must first classify the packet according to the network protocol it belongs, and later check that every header of every layer is well defined (e.g.: Ethernet, IP and TCP). Only after that, the headers can be modified.
|
||||
|
||||
If the program also wants to modify the packet payload, then it must be checked to be between the bounds of the packet and well defined according to the packet headers(using fields IHL, packet length and data offset, in figure \ref{fig:frame}). Also, after using any of the helpers that enlarge or reduce the size of the packet, all check operations must be repeated before any subsequent operation.
|
||||
If the program also wants to modify the packet payload, then it must be checked to be between the bounds of the packet and well defined according to the packet headers (using fields IHL, packet length and data offset, in Figure \ref{fig:frame}). Also, after using any of the helpers that enlarge or reduce the size of the packet, all check operations must be repeated before any subsequent operation.
|
||||
|
||||
Finally, note that after any modification in the packet, some network protocols (such as IP and TCP) require to recalculate their checksum fields.
|
||||
|
||||
@@ -333,26 +333,26 @@ Finally, note that after any modification in the packet, some network protocols
|
||||
|
||||
Having the previous restrictions in mind, we can find multiple possible malicious uses of an XDP/TC program:
|
||||
\begin{itemize}
|
||||
\item \textbf{Spy all network connections} in the system. An XDP or TC ingress program can read any packet from any interface, therefore achieving a comprehensive view on which are the running communications and opened ports (even if protocols with encryption are being used) and gathering transmitted data (if the connection is also in plaintext).
|
||||
\item \textbf{Hide arbitrary traffic} from the host. If an XDP program drops a packet, the kernel will not be able to know any packet was received in the first place. This can be used to hide malicious incoming traffic. However, as we will mention in section \ref{section:c2}, malicious traffic may still be detected by other external devices, such as network-wide firewalls.
|
||||
\item \textbf{Monitor all network connections} in the system. An XDP or TC ingress program can read any packet from any interface, therefore achieving a comprehensive view on which are the running communications and opened ports (even if protocols with encryption are being used) and gathering transmitted data (if the connection is also in plaintext).
|
||||
\item \textbf{Hide arbitrary traffic} from the host. If an XDP program drops a packet, the kernel will not be able to know any packet was received in the first place. This can be used to hide malicious incoming traffic. However, as we will mention in Section \ref{section:c2}, malicious traffic may still be detected by other external devices, such as network-wide firewalls.
|
||||
\item \textbf{Modify incoming traffic} with XDP programs. Every packet can be modified (as we mentioned at the beginning of section \ref{section:abusing_networking}), and any modification will be unnoticeable to the kernel, meaning that we will have complete, invisible control over the packets received by the kernel.
|
||||
\item \textbf{Modify outgoing traffic} with TC egress programs. Since every packet can be modified at will, we will therefore have complete control over any packet sent by the host. This can be used to enable a malicious program to communicate over the network and exfiltrate data, since even if we cannot create a new connection from eBPF, we can still modify existing packets, writing any payload and headers on it (thus being able to, for instance, change the destination of the packet).
|
||||
|
||||
Notice, however, that these modifications are not transparent to the kernel as with XDP, and thus an internal firewall may detect our malicious traffic.
|
||||
\end{itemize}
|
||||
|
||||
Although we mention the possibility of modifying outgoing traffic as an alternative to the impossibility of sending new packets from eBPF, there exists a major disadvantage by doing this, since the original packet of the application will be lost, and we will thus be disrupting the normal functioning of the system (which in a rootkit is unacceptable, as we mentioned in section \ref{section:motivation}, stealth is a priority).
|
||||
Although we mention the possibility of modifying outgoing traffic as an alternative to the impossibility of sending new packets from eBPF, there exists a major disadvantage by doing this, since the original packet of the application will be lost, and we will thus be disrupting the normal functioning of the system (which in a rootkit is unacceptable, as we mentioned in Section \ref{section:motivation}, stealth is a priority).
|
||||
|
||||
There exists, however, a simple way of duplicating a packet so that the original packet is not lost but we can still send our overwritten packet. This technique, first presented by Guillaume Fournier and Sylvain Afchainthe in their DEFCON talk, consists of taking advantage of TCP retransmissions we described on section \ref{subsection:tcp}. Figure \ref{fig:tcp_exfiltrate_retrans} shows this process:
|
||||
There exists, however, a simple way of duplicating a packet so that the original packet is not lost but we can still send our overwritten packet. This technique, first presented by Guillaume Fournier and Sylvain Afchain in their DEFCON talk, consists of taking advantage of TCP retransmissions we described on Section \ref{subsection:tcp}. Figure \ref{fig:tcp_exfiltrate_retrans} shows this process:
|
||||
|
||||
\begin{figure}[H]
|
||||
\centering
|
||||
\includegraphics[width=15cm]{tcp_exfiltrate_retrans.jpg}
|
||||
\caption{Technique to duplicate a packet for exfiltrating data.}
|
||||
\caption{TCP retransmissions technique to duplicate a packet for exfiltrating data.}
|
||||
\label{fig:tcp_exfiltrate_retrans}
|
||||
\end{figure}
|
||||
|
||||
In the figure, we can observe a host infected by a malicious TC egress program. An user space application at some point needs to send a packet (in this case a simple ping), and the TC program will overwrite it (in this case, it writes a password which it has been able to find, and substitutes the destination IP address with that of a listening attacker.
|
||||
In the figure, we can observe a host infected by a malicious TC egress program. An user space application at some point needs to send a packet (in this case a simple ping), and the TC program will overwrite it (in this case, it writes a password which it has been able to find, and substitutes the destination IP address with that of a listening attacker).
|
||||
After the timer runs out, the TCP protocol itself will retransmit the same packet as previously and thus the original data is delivered too.
|
||||
|
||||
Using this technique, we will be able to send our own packets every time an application sends outgoing traffic. And, unless the network is being monitored, this attack will go unnoticed, provided that the delay of the original packet is similar to that when a single packet lost.
|
||||
@@ -365,4 +365,3 @@ Ultimately, the capabilities discussed in this section unlock complete freedom f
|
||||
\item A \textbf{backdoor}, a stealthy program which listens on the network interface and waits for secret instructions from a remote attacker-controlled client program. This backdoor can have \textbf{Command and Control (C2)} capabilities, meaning that it can process commands sent by the attacker and received at the backdoor, executing a series of actions corresponding to the request received, and (when needed) answering the attacker with the result of the command.
|
||||
\end{itemize}
|
||||
|
||||
%TODO maybe a conclusion for this chapter?
|
||||
|
||||
@@ -16,12 +16,17 @@ We will firstly present an overview on the rootkit architecture and design. Afte
|
||||
\section{Rootkit architecture} \label{section:rootkit_arch}
|
||||
Figure \ref{fig:rootkit} shows an overview of the rootkit modules and components which have been built for this research work.
|
||||
|
||||
\newgeometry{hmargin=3cm,vmargin=2cm}
|
||||
\thispagestyle{lscape}
|
||||
\begin{landscape}
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
\includegraphics[width=15.5cm]{rootkit.png}
|
||||
\caption{Overview of the rootkit subsystems and components.}
|
||||
\includegraphics[width=21cm]{rootkit.png}
|
||||
\caption{Overview of the rootkit modules and components.}
|
||||
\label{fig:rootkit}
|
||||
\end{figure}
|
||||
\end{landscape}
|
||||
\restoregeometry
|
||||
|
||||
As we can observe in the figure, we can distinguish 6 different rootkit modules, along with a rootkit client which provides remote control of the rootkit over the network from the attacker machine. Also, there exists a rootkit user space process, which is listening for commands issued from the kernel-side, transmitted through a ring buffer.
|
||||
\begin{itemize}
|
||||
@@ -51,7 +56,7 @@ Apart from the network triggers, upon receiving a response by the backdoor the r
|
||||
\end{itemize}
|
||||
|
||||
|
||||
With respect to how the rootkit implementation is distributed into multiple programs, we can find that, overall, there exist 4 main components, as shown in figure \ref{fig:rootkit_files}.
|
||||
With respect to how the rootkit implementation is distributed into multiple programs, we can find that, overall, there exist 4 main components, as shown in Figure \ref{fig:rootkit_files}.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -77,21 +82,18 @@ This program is also responsible of creating the shared map which the backdoor w
|
||||
|
||||
|
||||
\section{Library injection module} \label{section:lib_injection}
|
||||
In this section, we will discuss how to hijack a user process running in the system so that it executes arbitrary code instructed from an eBPF program. For this, we will be injecting a library which will be executed by taking advantage of the fact that the GOT section in ELFs is flagged as writable (as we introduced in section \ref{subsection:elf_lazy_binding} and using the stack scanning technique covered in section \ref{subsection:bpf_probe_write_apps}. This injection will be stealthy (it must not crash the process) and will be able to hijack privileged programs such as systemd, so that the code is executed as root.
|
||||
In this section, we will discuss how to hijack a user process running in the system so that it executes arbitrary code instructed from an eBPF program. For this, we will be injecting a library which will be executed by taking advantage of the fact that the GOT section in ELFs is flagged as writable (as we introduced in section \ref{subsection:elf_lazy_binding} and using the stack scanning technique covered in Section \ref{subsection:bpf_probe_write_apps}. This injection will be stealthy (it must not crash the process) and will be able to hijack privileged programs such as systemd, so that the code is executed as root.
|
||||
|
||||
We will also research how to circumvent the protections which modern compilers have set in order to prevent similar attacks (when performed without eBPF), as we overview in section \ref{subsection:hardening_elf}.
|
||||
We will also research how to circumvent the protections which modern compilers have set in order to prevent similar attacks (when performed without eBPF), as we overview in Section \ref{subsection:hardening_elf}.
|
||||
|
||||
This technique has some advantages and disadvantages to the one described by Jeff Dileo at DEFCON 27 \cite{evil_ebpf_p6974}, which we will briefly cover before presenting ours. Both techniques will be later compared in chapter \ref{chapter:related_work}.
|
||||
This technique has some advantages and disadvantages to the one described by Jeff Dileo at DEFCON 27 \cite{evil_ebpf_p6974}, which we will briefly cover before presenting ours. Both techniques will be later compared in Chapter \ref{chapter:related_work}.
|
||||
|
||||
|
||||
\subsection{ROP with eBPF} \label{subsection:rop_ebpf}
|
||||
In 2019, Jeff Dileo presented in DEFCON 27 the first technique to achieve arbitrary code execution using eBPF \cite{evil_ebpf_p6974}. For this, he used the ROP technique we described in section \ref{subsection:rop} to inject malicious code into a process. We will present an overview on his technique, in order to later compare it to the one we will develop for our rootkit and find advantages and disadvantages. Note that this is a summary and some aspects have been simplified, however we will go in full detail during the explanation of our own technique.
|
||||
In 2019, Jeff Dileo presented in DEFCON 27 the first technique to achieve arbitrary code execution using eBPF \cite{evil_ebpf_p6974}. For this, he used the ROP technique we described in Section \ref{subsection:rop} to inject malicious code into a process. We will present an overview on his technique, in order to later compare it to the one we will develop for our rootkit and find advantages and disadvantages. Note that this is a summary and some aspects have been simplified, however we will go in full detail during the explanation of our own technique.
|
||||
|
||||
Figure \ref{fig:rop_evil_ebpf_1} shows an overview on the process memory and the eBPF programs loaded. For this injection, we will use the stack scanning technique (section \ref{subsection:bpf_probe_write_apps}) using the arguments of a system call whose arguments are passed using the stack (sys\_timerfd\_settime, which receives two structs utmr and otmr). Therefore, a kprobe is attached to the system call, so that it can start to scan for the return address of the system call, which we know is the original value of register rip which was pushed into the stack (ret).
|
||||
Figure \ref{fig:rop_evil_ebpf_1} shows an overview on the process memory and the eBPF programs loaded. For this injection, we will use the stack scanning technique (Section \ref{subsection:bpf_probe_write_apps}) using the arguments of a system call whose arguments are passed using the stack (sys\_timerfd\_settime, which receives two structs utmr and otmr). Therefore, a kprobe is attached to the system call, so that it can start to scan for the return address of the system call, which we know is the original value of register rip which was pushed into the stack (ret).
|
||||
|
||||
%TODO This figure needs a remodel. I tried to keep it simple to explain the main concepts on the technique described afterwards, but after writing the next section I realised it gets some things wrong:
|
||||
% - It does not show .got and .plt sections.
|
||||
% - It shows the RBP register in an incorrect place.
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
\includegraphics[width=15cm]{rop_evil_ebpf_1.jpg}
|
||||
@@ -99,8 +101,7 @@ Figure \ref{fig:rop_evil_ebpf_1} shows an overview on the process memory and the
|
||||
\label{fig:rop_evil_ebpf_1}
|
||||
\end{figure}
|
||||
|
||||
%TODO I don't quite like this. Maybe the glibc bit, because of its importance, is better somewhere else
|
||||
An additional aspect must be introduced now (we will cover it more in detail in section \ref{TODO}): system calls are not directly called by the instructions in the .text section, but rather user programs in C make use of the C Standard Library to delegate the actual syscall, which in this case is the GNU Standard Library (glibc) \cite{glibc}. Therefore, a program calls a function in glibc (in this case timerfd\_settime) in which the syscall is performed, and the kernel executes it.
|
||||
An additional aspect must be introduced now (we will cover it more in detail in section \ref{subsection:got_attack}): system calls are not directly called by the instructions in the .text section, but rather user programs in C make use of the C Standard Library to delegate the actual syscall, which in this case is the GNU Standard Library (glibc) \cite{glibc}. Therefore, a program calls a function in glibc (in this case timerfd\_settime) in which the syscall is performed, and the kernel executes it.
|
||||
|
||||
This means that, during the stack scanning technique, if we start from struct utmr and scan forward in the stack, what we will find in ret is the return address of the PLT stub that calls the function at glibc, and not directly that of the syscall to the kernel. Therefore, our goal is, for every data in the stack while scanning forward, check whether it is the real return address of the PLT stub we are looking for. For an address to be the real return address, we will follow the next steps:
|
||||
\begin{enumerate}
|
||||
@@ -118,7 +119,7 @@ Now that we have found the return address, we save a backup of the stack (to rec
|
||||
\label{fig:rop_evil_ebpf_2}
|
||||
\end{figure}
|
||||
|
||||
As we can see in the figure, the function has already exited, and ret has been popped into register rip. As we explained in section \ref{subsection:rop}, the attacker places in that position the address of the first ROP gadget. After that, the attacker can execute arbitrary code. Jeff Dileo, for instance, loads a malicious library into the process (we will do the same and explain this process in the next sections).
|
||||
As we can see in the figure, the function has already exited, and ret has been popped into register rip. As we explained in Section \ref{subsection:rop}, the attacker places in that position the address of the first ROP gadget. After that, the attacker can execute arbitrary code. Jeff Dileo, for instance, loads a malicious library into the process (we will do the same and explain this process in the next sections).
|
||||
|
||||
Once the attacker has finished executing the injected code, the stack must be restored to the original position so that the program can continue without crashing. A simplified view of this procedure consists of attaching a kprobe to a random system call (in this case, sys\_close()) so that, from the ROP code, we can alert the eBPF program when it is time to remove the ROP code and restore the original stack. Figure \ref{fig:rop_evil_ebpf_3} shows this final step:
|
||||
|
||||
@@ -129,17 +130,17 @@ Once the attacker has finished executing the injected code, the stack must be re
|
||||
\label{fig:rop_evil_ebpf_3}
|
||||
\end{figure}
|
||||
|
||||
As we can see, eBPF writes back the original stack and thus the execution can continue. Note that, in practice, some final gadgets must also be executed in order to restore the state of rip and rsp, the stack data for this is written in the free memory zone, so that it does not need to be removed.
|
||||
As we can see, eBPF writes back the original stack and thus the execution can continue. Note that, in practice, some final gadgets must also be executed in order to restore the state of rip and rsp. The stack data for this is written in the free memory zone, so that it does not need to be removed.
|
||||
|
||||
|
||||
|
||||
%TODO Eligible to writing more. This was merged with the explanation of each feature before, so it was more extense, but now it might need some more info??
|
||||
\subsection{Bypassing hardening features in ELFs} \label{subsection:hardening_bypass}
|
||||
During section \ref{subsection:hardening_elf}, we presented multiple security hardening measures that have been introduced to prevent common exploitation techniques (such as stack buffer overflows) and that nowadays can be incorporated, usually by default, in ELF binaries generated using modern compilers. We will now explore how to bypass these features, so that we can design an injection technique that can target any process in the system, independently on whether it was compiled using these mitigations.
|
||||
During Section \ref{subsection:hardening_elf}, we presented multiple security hardening measures that have been introduced to prevent common exploitation techniques (such as stack buffer overflows) and that nowadays can be incorporated, usually by default, in ELF binaries generated using modern compilers. We will now explore how to bypass these features, so that we can design an injection technique that can target any process in the system, independently on whether it was compiled using these mitigations.
|
||||
|
||||
\textbf{Stack canaries}\\
|
||||
Since stack canaries will be checked after the vulnerable function returns, an attacker seeking to overwrite the stack must ensure that the value of the canary remains constant. In the context of a buffer overflow attack, this can be achieved by leaking the value of the canary and incorporating it into the overflowing data at the stack, so that the same value is written on the same address \cite{canary_exploit}.
|
||||
|
||||
In our rootkit, unlike in the ROP technique presented in section \ref{subsection:rop_ebpf}, we will avoid overwriting the value of the saved rip in the stack completely. Therefore, as long as our eBPF program leaves all registers and stack data in the same state as before calling the function, we will not trigger any alerts.
|
||||
In our rootkit, unlike in the ROP technique presented in Section \ref{subsection:rop_ebpf}, we will avoid overwriting the value of the saved rip in the stack completely. Therefore, as long as our eBPF program leaves all registers and stack data in the same state as before calling the function, we will not trigger any alerts.
|
||||
|
||||
\textbf{DEP/NX}\\
|
||||
The only alternative for an attacker upon a non-executable stack is either injecting shellcode at any other executable memory address, or the use of advanced techniques like ROP that fully circumvent this mitigation since the data at the stack is not directly executed at any step.
|
||||
@@ -147,14 +148,14 @@ The only alternative for an attacker upon a non-executable stack is either injec
|
||||
In our rootkit, we will choose the first option, scanning the process virtual memory for an executable page where we will inject our shellcode. This process is usually known as finding 'code caves'.
|
||||
|
||||
\textbf{ASLR}\\
|
||||
In order to bypass ASLR, attackers must take into account that, although the address at which, for instance, a library is loaded is random, the internal structure of the library remains unchanged, with all symbols in the same relative position, as figure \ref{table:aslr_offset} shows.
|
||||
In order to bypass ASLR, attackers must take into account that, although the address at which, for instance, a library is loaded is random, the internal structure of the library remains unchanged, with all symbols in the same relative position, as Figure \ref{fig:aslr_offset} shows.
|
||||
|
||||
%TODO Add the .data section here
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
\includegraphics[width=13cm]{aslr_offset.jpg}
|
||||
\caption{Two runs of the same executable using ASLR, showing a library and two symbols.}
|
||||
\label{fig:alsr_offset}
|
||||
\label{fig:aslr_offset}
|
||||
\end{figure}
|
||||
|
||||
As we can observe in the figure, although glibc is loaded at a different base address each run, the offset between the functions it implements, malloc() and free(), remains constant. Therefore, a method for bypassing ASLR is to gather information about the absolute address of any symbol, which can then easily lead to knowing the address of any other if the attacker decompiles the executable and calculates the offset between a pair of addresses where one is known. This is the chosen method for our technique.
|
||||
@@ -169,13 +170,13 @@ In our rootkit, we will directly write using eBPF the value of GOT if it was com
|
||||
|
||||
|
||||
\subsection{Library injection via GOT hijacking} \label{subsection:got_attack}
|
||||
Taking into account the previous background and that about stack attacks, ELF's lazy binding and hardening features for binaries we presented in section \ref{section:elf}, we will now present the exploitation technique incorporated in our rootkit to inject a malicious library into a running process.
|
||||
Taking into account the previous background and that about stack attacks, ELF's lazy binding and hardening features for binaries we presented in Section \ref{section:elf}, we will now present the exploitation technique incorporated in our rootkit to inject a malicious library into a running process.
|
||||
|
||||
This attack is based on the possibility of overwriting the data at the GOT section. As we have mentioned previously, this section is marked as writeable if the program was compiled using Partial RELRO, meaning that we will be able to overwrite its value from an eBPF program using the helper bpf\_probe\_write\_user(). After modifying the value of GOT, a PLT stub will take the new value as the jump address (as we explained in section \ref{subsection:elf_lazy_binding}), effectively hijacking the flow of execution of the program. In the case that a program was compiled with Full RELRO (which will be the case of many programs running by default in a Linux system such as systemd), we will make use of the /proc filesystem for overwriting this value.
|
||||
This attack is based on the possibility of overwriting the data at the GOT section. As we have mentioned previously, this section is marked as writeable if the program was compiled using Partial RELRO, meaning that we will be able to overwrite its value from an eBPF program using the helper bpf\_probe\_write\_user(). After modifying the value of GOT, a PLT stub will take the new value as the jump address (as we explained in Section \ref{subsection:elf_lazy_binding}), effectively hijacking the flow of execution of the program. In the case that a program was compiled with Full RELRO (which will be the case of many programs running by default in a Linux system such as systemd), we will make use of the /proc filesystem for overwriting this value.
|
||||
|
||||
The rootkit will inject the library once an specific syscall is called by a process, but the library injection will only happen after the second syscall, since we need to wait for the GOT address to be loaded by the dynamic linker. This is a necessary step because eBPF will need to validate that it really is the GOT section to overwrite.
|
||||
|
||||
This technique works both in compilers with low hardening fetaures by default (Clang) and also on a compiler with all of them active (GCC), see table \ref{table:compilers}. On each of the steps, we will detail the different existing methods depending on the compiler features.
|
||||
This technique works both in compilers with low hardening fetaures by default (Clang) and also on a compiler with all of them active (GCC), see Table \ref{table:compilers}. On each of the steps, we will detail the different existing methods depending on the compiler features.
|
||||
|
||||
For this research work, the rootkit is prepared to perform this attack on any process that makes use of either the system call sys\_openat or sys\_timerfd\_settime, which are called by the standard library glibc.
|
||||
|
||||
@@ -191,7 +192,7 @@ We will now describe the multiple exploitation stages for our technique. Figure
|
||||
\textbf{Stage 1: eBPF tracing and scan the stack}\\
|
||||
We load and attach a tracepoint eBPF program at the \textit{enter} position of syscall sys\_timerfd\_settime. Firstly, we must ensure that the process calling the tracepoint is one of the processes to hijack.
|
||||
|
||||
We will then proceed with the stack scanning technique, as we explained in section \ref{subsection:bpf_probe_write_apps}. In this case, we will take one of the syscall parameters and scan forward in the stack. For each iteration, we must check if the data at the stack corresponds to the saved return address of the PLT stub that jumps to glibc where the syscall sys\_timerfd\_settime is called. Figure \ref{fig:lib_stage1} shows an overview of how these call instructions relate each memory section.
|
||||
We will then proceed with the stack scanning technique, as we explained in Section \ref{subsection:bpf_probe_write_apps}. In this case, we will take one of the syscall parameters and scan forward in the stack. For each iteration, we must check if the data at the stack corresponds to the saved return address of the PLT stub that jumps to glibc where the syscall sys\_timerfd\_settime is called. Figure \ref{fig:lib_stage1} shows an overview of how these call instructions relate each memory section.
|
||||
|
||||
|
||||
\begin{figure}[htbp]
|
||||
@@ -203,7 +204,7 @@ We will then proceed with the stack scanning technique, as we explained in secti
|
||||
|
||||
The following are the steps we will follow to perform check some data at the stack is the saved return address:
|
||||
\begin{enumerate}
|
||||
\item Check that the previous instruction is a call instruction, by checking the instruction length and opcodes (call instructions always start with e8, and the length is 5 bytes, see figure \ref{fig:firstcall}).
|
||||
\item Check that the previous instruction is a call instruction, by checking the instruction length and opcodes (call instructions always start with e8, and the length is 5 bytes, see Figure \ref{fig:firstcall}).
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
\includegraphics[width=13cm]{sch_firstcall.png}
|
||||
@@ -211,7 +212,7 @@ The following are the steps we will follow to perform check some data at the sta
|
||||
\label{fig:firstcall}
|
||||
\end{figure}
|
||||
\item Now that we know we localized a call instruction, we take the address at which it jumps. That should be an address in a PLT stub.
|
||||
\item We analyse the instructions at the PLT stub. If the program was compiled with GCC, the first instruction will be an \textit{endbr64} instruction followed by the PLT jump instruction using the address at GOT (see figure \ref{fig:plt_gcc}), since it generates Intel CET-compatible programs. Otherwise, if using Clang, which does not generate Intel CET instructions, the first instruction is the PLT jump (see figure \ref{fig:plt_clang}).
|
||||
\item We analyse the instructions at the PLT stub. If the program was compiled with GCC, the first instruction will be an \textit{endbr64} instruction followed by the PLT jump instruction using the address at GOT (see Figure \ref{fig:plt_gcc}), since it generates Intel CET-compatible programs. Otherwise, if using Clang, which does not generate Intel CET instructions, the first instruction is the PLT jump (see Figure \ref{fig:plt_clang}).
|
||||
|
||||
We analyse the jump instruction and, again, take the address at which it jumps. This time, it should be the address of the function at glibc.
|
||||
\begin{figure}[htbp]
|
||||
@@ -227,7 +228,7 @@ We analyse the jump instruction and, again, take the address at which it jumps.
|
||||
\label{fig:plt_clang}
|
||||
\end{figure}
|
||||
|
||||
\item We now have the address of timerfd\_settime at glibc, from where the syscall will be called. From eBPF, we continue to scan the first opcodes and compare them to those we expect to find at glibc. Specifically, the function would have to contain the instruction opcodes shown in figure \ref{fig:settime_glibc}. Note that, in our version of Ubuntu, we will find Glibc compiled with GCC.
|
||||
\item We now have the address of timerfd\_settime at glibc, from where the syscall will be called. From eBPF, we continue to scan the first opcodes and compare them to those we expect to find at glibc. Specifically, the function would have to contain the instruction opcodes shown in Figure \ref{fig:settime_glibc}. Note that, in our version of Ubuntu, we will find Glibc compiled with GCC.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -242,7 +243,7 @@ Once we ensured we reached the correct glibc function, we are now sure that the
|
||||
|
||||
Our rootkit also incorporates an alternative scanning technique for processes calling the syscall sys\_openat(). This technique enables to scan the stack even when the system call does not incorporate any arguments from the userspace (and thus we cannot take them from our eBPF tracing program to use them as a foothold in the stack).
|
||||
|
||||
As we explained in section \ref{subsection:tracing_arguments}, tracepoint programs receive an struct pt\_regs pointer as an argument. We can take this struct and use the value of register rbp as our starting point for scanning the stack. As we can see on figures \ref{fig:plt_clang}, \ref{fig:plt_gcc} and \ref{fig:settime_glibc}, the PLT does not contain any function prologue (it does not modify the value of rsp) and the function at glibc does not change this value either. Therefore, in our eBPF program, since we are hooking the syscall at the beginning of its execution, the value of rbp will be the original frame pointer before calling the PLT, and therefore we can use it as our starting address for stack scan, proceeding to scan forward until we find the saved return address.
|
||||
As we explained in Section \ref{subsection:tracing_arguments}, tracepoint programs receive an struct pt\_regs pointer as an argument. We can take this struct and use the value of register rbp as our starting point for scanning the stack. As we can see on Figures \ref{fig:plt_clang}, \ref{fig:plt_gcc} and \ref{fig:settime_glibc}, the PLT does not contain any function prologue (it does not modify the value of rsp) and the function at glibc does not change this value either. Therefore, in our eBPF program, since we are hooking the syscall at the beginning of its execution, the value of rbp will be the original frame pointer before calling the PLT, and therefore we can use it as our starting address for stack scan, proceeding to scan forward until we find the saved return address.
|
||||
|
||||
\textbf{Stage 2: Programming shellcode}\\
|
||||
Once that we have the address of the GOT section, we need to prepare our shellcode to be injected into the process memory. We will overwrite the value at GOT and redirect the flow of execution to the address at which our shellcode is stored in memory.
|
||||
@@ -250,9 +251,9 @@ Once that we have the address of the GOT section, we need to prepare our shellco
|
||||
Since we want our shellcode to be able to load a library, it will need to call the function \_\_libc\_dlopen\_mode, which can be found in glibc. This function expects to receive as an argument a string with the file path of the malicious library, and therefore the shellcode will also need to call \_\_libc\_malloc to allocate space for the argument. Tables \ref{table:libc_malloc} and \ref{table:libc_dlopen_mode} explain the expected arguments and return value of each function in detail.
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{4cm}|>{\centering\arraybackslash}p{10cm}|}
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{3cm}|>{\centering\arraybackslash}p{10cm}|}
|
||||
\hline
|
||||
Register & Value\\
|
||||
\textbf{REGISTER} & \textbf{VALUE}\\
|
||||
\hline
|
||||
\hline
|
||||
edi & Number of bytes to allocate. \\
|
||||
@@ -265,9 +266,9 @@ rax & Return value, contains the address at which the requested bytes were alloc
|
||||
\end{table}
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{4cm}|>{\centering\arraybackslash}p{10cm}|}
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{3cm}|>{\centering\arraybackslash}p{10cm}|}
|
||||
\hline
|
||||
Register & Value\\
|
||||
\textbf{REGISTER} & \textbf{VALUE}\\
|
||||
\hline
|
||||
\hline
|
||||
rsi & 0x1, indicating flag RTLD\_LAZY\\
|
||||
@@ -279,7 +280,7 @@ rdi & Address where to read path of library to load\\
|
||||
\label{table:libc_dlopen_mode}
|
||||
\end{table}
|
||||
|
||||
The programs were compiled having ASLR active, and therefore we cannot know the virtual address at which these functions are loaded into the process memory. However, since we have leaked the address of timerfd\_settime at glibc with the previous eBPF scan, we can calculate the address of the other functions, as we introduced in section \ref{subsection:hardening_bypass}. Figure \ref{fig:aslr_bypass_example} shows an example of this process.
|
||||
The programs were compiled having ASLR active, and therefore we cannot know the virtual address at which these functions are loaded into the process memory. However, since we have leaked the address of timerfd\_settime at glibc with the previous eBPF scan, we can calculate the address of the other functions, as we introduced in Section \ref{subsection:hardening_bypass}. Figure \ref{fig:aslr_bypass_example} shows an example of this process.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -317,13 +318,13 @@ Once we have developed our shellcode, and before overwriting the value of GOT, w
|
||||
|
||||
Because of DEP/NX, we cannot use the stack for executing code. On top of that, as we can observe in the section header dump at Appendix \ref{annexsec:readelf_sec_headers}, for security reasons all sections are nowadays marked either writeable or executable, but never both simultaneously.
|
||||
|
||||
Therefore, we will use the proc filesystem which we introduced in section \ref{section:proc_filesystem}. By using the file under \textit{/proc/<pid>/maps}, we will easily identify the address range of those memory sections marked as executable, and by using the file \textit{/proc/<pid>/mem}, we will write our shellcode into that memory section, bypassing the absence of a write flag.
|
||||
Therefore, we will use the proc filesystem which we introduced in Section \ref{section:proc_filesystem}. By using the file under \textit{/proc/<pid>/maps}, we will easily identify the address range of those memory sections marked as executable, and by using the file \textit{/proc/<pid>/mem}, we will write our shellcode into that memory section, bypassing the absence of a write flag.
|
||||
|
||||
Although we may write freely into any virtual address using this technique, as we saw in section \ref{subsection:proc_maps} executable memory usually corresponds to the .text section. Therefore, we are at risk of overwriting critical instructions of the program. This is the reason why we must search for empty memory spaces inside the virtual memory, called code caves.
|
||||
Although we may write freely into any virtual address using this technique, as we saw in Section \ref{subsection:proc_maps} executable memory usually corresponds to the .text section. Therefore, we are at risk of overwriting critical instructions of the program. This is the reason why we must search for empty memory spaces inside the virtual memory, called code caves.
|
||||
|
||||
We will consider an appropriate code cave as a continuous memory space inside the .text section that consists of a series of NULL bytes (opcode 0x00). Although in principle this may seem like a rare occurence, it is a common find in most processes due to how memory access control is implemented.
|
||||
|
||||
In figure \ref{fig:proc_maps_sample}, we can observe how virtual memory sections have a length of 0x1000, or are a multiple of it. This is not an arbitrary number, but rather it is because memory sections must always be of length multiple of the system page length (4 KB = 0x1000 bytes). Therefore, the minimum granularity of a set of permissions over a memory section is of 0x1000 bytes.
|
||||
In Figure \ref{fig:proc_maps_sample}, we can observe how virtual memory sections have a length of 0x1000, or are a multiple of it. This is not an arbitrary number, but rather it is because memory sections must always be of length multiple of the system page length (4 KB = 0x1000 bytes). Therefore, the minimum granularity of a set of permissions over a memory section is of 0x1000 bytes.
|
||||
|
||||
Since sections must occupy a multiple of 1000 bytes, this leads to multiple sections which leave lots of empty, NULL bytes, unocuppied without any instructions. This is the reason why we will, quite probably, find a code cave in most processes.
|
||||
|
||||
@@ -414,7 +415,7 @@ Table \ref{table:sudoers_syscall} shows the parameters expected by these system
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|c|>{\centering\arraybackslash}p{8cm}|}
|
||||
\hline
|
||||
System call & Arguments\\
|
||||
\textbf{SYSTEM CALL} & \textbf{ARGUMENTS}\\
|
||||
\hline
|
||||
\hline
|
||||
\multirow{4}{*}{sys\_openat} & \multicolumn{1}{c|}{int dfd}\\
|
||||
@@ -436,7 +437,7 @@ System call & Arguments\\
|
||||
\label{table:sudoers_syscall}
|
||||
\end{table}
|
||||
|
||||
The table shows that there exist two arguments marked as \textit{\_\_user}, which, as we explained in section \ref{subsection:bpf_probe_write_apps}, can be overwritten from an eBPF tracing program using the helper bpf\_probe\_write\_user(). Therefore, there exist two different attack vectors:
|
||||
The table shows that there exist two arguments marked as \textit{\_\_user}, which, as we explained in Section \ref{subsection:bpf_probe_write_apps}, can be overwritten from an eBPF tracing program using the helper bpf\_probe\_write\_user(). Therefore, there exist two different attack vectors:
|
||||
\begin{itemize}
|
||||
\item Modify the argument \textit{filename}, so that the sudo process opens a fake, crafted sudoers file. In this file we would write the entries needed for our user to have sudo privilege without a password. Since the sys\_open syscall returns a file descriptor, which is later used by sys\_read, that is the only argument needed to be modified.
|
||||
\item Modify the buffer \textit{buf} in the sys\_read syscall so that it returns specially crafted data to the sudo program.
|
||||
@@ -456,7 +457,7 @@ As we can observe in the figure, we will use three eBPF tracepoints. The reason
|
||||
\begin{itemize}
|
||||
\item An \textit{enter} tracepoint at sys\_openat knows the file being opened, but it does not have access to the user buffer.
|
||||
\item An \textit{enter} tracepoint at sys\_read has access to the user buffer, but does not know the name of the file (it only has a file descriptor). Also, if it writes into the buffer now, it will be overwritten later when the kernel reads the \textit{/etc/sudoers} file.
|
||||
\item An \textit{exit} tracepoint at sys\_read only receives the return value as a parameter (as we explained in section \ref{subsection:tracing_arguments}), but it can freely write to the user buffer if it had access to it, since the kernel already finished writing on it.
|
||||
\item An \textit{exit} tracepoint at sys\_read only receives the return value as a parameter (as we explained in Section \ref{subsection:tracing_arguments}), but it can freely write to the user buffer if it had access to it, since the kernel already finished writing on it.
|
||||
\end{itemize}
|
||||
|
||||
Taking the above into account, we designed the privilege escalation technique as follows:
|
||||
@@ -471,11 +472,11 @@ Taking the above into account, we designed the privilege escalation technique as
|
||||
\item A process name.
|
||||
\item A filename.
|
||||
\end{itemize}
|
||||
The key of the map fs\_open is the PID of the user process from which the call to an eBPF program originated, this can be obtained using the bpf\_get\_current\_pid\_tgid() helper (see section \ref{subsection:ebpf_helpers}).
|
||||
The key of the map fs\_open is the PID of the user process from which the call to an eBPF program originated, this can be obtained using the bpf\_get\_current\_pid\_tgid() helper (see Section \ref{subsection:ebpf_helpers}).
|
||||
\end{itemize}
|
||||
\item A malicious program we executed from user "osboxes" requests sudo privileges. Our goal is to let it run with privileged permissions without having to introduce a password. Note that, although in the system we are using osboxes is a user in the \textit{/etc/sudoers} file already (although requiring a password for running as sudo), this process also works if we used a user not included on it in the first place.
|
||||
|
||||
The sudo process opens the \textit{/etc/sudoers} file. The syscall is called and the sys\_enter\_openat tracepoint is called before the syscall is executed. We check that the syscall was called by the sudo process using the helper bpf\_get\_current\_comm() (see section \ref{subsection:ebpf_helpers}) and, if it is, write the filename into the fs\_open map. After that, the tracepoint exists and the syscall is executed.
|
||||
The sudo process opens the \textit{/etc/sudoers} file. The syscall is called and the sys\_enter\_openat tracepoint is called before the syscall is executed. We check that the syscall was called by the sudo process using the helper bpf\_get\_current\_comm() (see Section \ref{subsection:ebpf_helpers}) and, if it is, write the filename into the fs\_open map. After that, the tracepoint exists and the syscall is executed.
|
||||
|
||||
\item The sudo process now reads from the file descriptor of the file \textit{/etc/sudoers}. The sys\_enter\_read tracepoint is executed right before the syscall is called. In the tracepoint, we check if we can find an entry with a filename in the fs\_open map using the process PID as key (which is the same for all tracepoints, since they originated from the same sudo process). We now write address of the buffer supplied by the sudo process into the map.
|
||||
|
||||
@@ -504,12 +505,12 @@ This section describes how the rootkit can hijack the execution of programs. Alt
|
||||
\item Be transparent to the user space, that is, if we hijack the execution of a program so that another is run, the original program should be executed too with the least delay.
|
||||
\end{itemize}
|
||||
|
||||
This technique is based on the modification of the arguments of the system call sys\_execve, used to execute programs. When it is called, it causes the program that is currently being run to be completely replaced by the new executed program \cite{execve_man}. Its arguments are listed in table \ref{table:execve_args}
|
||||
This technique is based on the modification of the arguments of the system call sys\_execve, used to execute programs. When it is called, it causes the program that is currently being run to be completely replaced by the new executed program \cite{execve_man}. Its arguments are listed in Table \ref{table:execve_args}
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|c|>{\centering\arraybackslash}p{7cm}|}
|
||||
\hline
|
||||
Argument & Description\\
|
||||
\textbf{ARGUMENT} & \textbf{DESCRIPTION}\\
|
||||
\hline
|
||||
\hline
|
||||
const char \_\_user *filename & Path and filename of the file to execute\\
|
||||
@@ -523,7 +524,7 @@ const char \_\_user *const \_\_user *envp & NULL-terminated array with the envir
|
||||
\label{table:execve_args}
|
||||
\end{table}
|
||||
|
||||
As we can observe in the table, all of the arguments of the syscall are marked with the keyword \_\_user, and therefore as we explain in section \ref{subsection:bpf_probe_write_apps} these arguments can be overwritten using the eBPF helper bpf\_probe\_write\_user(). This opens for us the possibility of modifying these arguments so that another file is modified.
|
||||
As we can observe in the table, all of the arguments of the syscall are marked with the keyword \_\_user, and therefore as we explain in Section \ref{subsection:bpf_probe_write_apps} these arguments can be overwritten using the eBPF helper bpf\_probe\_write\_user(). This opens for us the possibility of modifying these arguments so that another file is modified.
|
||||
|
||||
Figure \ref{fig:summ_execve_hijack} summarizes the results of an attack using this rootkit module. As we can observe in the figure, we will hijack the execution of sys\_execve to run our own program, but as we mentioned we must execute the original program too in order not to raise concerns in the user space. Therefore, the malicious program must be able to access the original arguments of the sys\_execve call to execute the original program.
|
||||
|
||||
@@ -545,7 +546,7 @@ We have mentioned the possibility of overwriting the parameters of the sys\_exec
|
||||
\item The helper successfully overwrites a buffer but, with a single write operation, it has also modified the value of some other user buffer.
|
||||
\end{itemize}
|
||||
|
||||
The reason for this is that, as we covered in section \ref{subsection:bpf_probe_write_apps}, the bpf\_probe\_write\_user() helper fails to write any data in the occurence of a page fault. As we explained in section \ref{subsection:mem_faults}, minor memory faults are particularly common when executing a fork() of a process, since the child process will not get its page table completely copied from the parent, but will request the mapping once it is attempted to be read.
|
||||
The reason for this is that, as we covered in Section \ref{subsection:bpf_probe_write_apps}, the bpf\_probe\_write\_user() helper fails to write any data in the occurence of a page fault. As we explained in Section \ref{subsection:mem_faults}, minor memory faults are particularly common when executing a fork() of a process, since the child process will not get its page table completely copied from the parent, but will request the mapping once it is attempted to be read.
|
||||
|
||||
Because programs calling sys\_execve will be completely replaced by the new program, we can find this function used commonly in two contexts:
|
||||
\begin{itemize}
|
||||
@@ -553,17 +554,17 @@ Because programs calling sys\_execve will be completely replaced by the new prog
|
||||
\item Programs that are run by the user in the command-line interface. Once a command is introduced, the program corresponding to the command is searched, and the bash process (or any other shell being used) will fork() itself and execute the new program.
|
||||
\end{itemize}
|
||||
|
||||
Therefore, when modifying the arguments of sys\_execve, we will find that most calls are from programs which had executed fork() previously, thus having a high probability of failing. Note that the exact reason why writing one buffer with bpf\_probe\_write\_user() modifies multiple buffers simultaneously is unknown, but it is a situation we must account for, since we cannot trust in the helper not returning an error, we must check the result of this write accesses.
|
||||
Therefore, when modifying the arguments of sys\_execve, we will find that most calls are from programs which had executed fork() previously, thus having a high probability of failing. Note that the exact reason why writing one buffer with bpf\_probe\_write\_user() modifies multiple buffers simultaneously is unknown (and possibly undefined behaviour), but it is a situation we must account for, since we cannot trust in the helper not returning an error, we must check the result of this write accesses.
|
||||
|
||||
\subsection{Hiding data in a system call}
|
||||
Apart from having to take into account that the bpf\_probe\_write\_user helper may fail in unexpected manners as we described, we also need to give special attention to how we will preserve the original information of the program being executed via sys\_execve after we modify the arguments of this call. As we showed in figure \ref{fig:summ_execve_hijack}, the malicious program executed using the hijacked syscall must be able to execute the original program. For this, the program will fork() and create a child process, on which execve() will be called with the original program arguments. Therefore, the main issue would be how to recover the original arguments once they were overwritten by eBPF.
|
||||
Apart from having to take into account that the bpf\_probe\_write\_user() helper may fail in unexpected manners as we described, we also need to give special attention to how we will preserve the original information of the program being executed via sys\_execve after we modify the arguments of this call. As we showed in Figure \ref{fig:summ_execve_hijack}, the malicious program executed using the hijacked syscall must be able to execute the original program. For this, the program will fork() and create a child process, on which execve() will be called with the original program arguments. Therefore, the main issue would be how to recover the original arguments once they were overwritten by eBPF.
|
||||
|
||||
In order to achieve this, we will hide the original arguments in those passed to the malicious program. Table \ref{fig:execve_args_hide} shows how this process works with a sample sys\_execve call. Environment variables have been omitted for simpleness, but we can usually find a large array of them.
|
||||
In order to achieve this, we will hide the original arguments in those passed to the malicious program. Table \ref{table:execve_args_hide} shows how this process works with a sample sys\_execve call. Environment variables have been omitted for simpleness, but we can usually find a large array of them.
|
||||
|
||||
\begin{table}[H]
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{2cm}|>{\centering\arraybackslash}p{3cm}|}
|
||||
\hline
|
||||
\multicolumn{2}{|c|}{Original arguments}\\
|
||||
\multicolumn{2}{|c|}{\textbf{ORIGINAL ARGUMENTS}}\\
|
||||
\hline
|
||||
\hline
|
||||
filename & "/bin/ls"\\
|
||||
@@ -578,9 +579,9 @@ envp[0] & NULL\\
|
||||
\hline
|
||||
\end{tabular}
|
||||
\quad
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{2cm}|>{\centering\arraybackslash}p{3cm}|}
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{2cm}|>{\centering\arraybackslash}p{4cm}|}
|
||||
\hline
|
||||
\multicolumn{2}{|c|}{Modified arguments}\\
|
||||
\multicolumn{2}{|c|}{\textbf{MODIFIED ARGUMENTS}}\\
|
||||
\hline
|
||||
\hline
|
||||
filename & "/home/osboxes/execve\_hijack"\\
|
||||
@@ -629,7 +630,7 @@ As we can observe in the figure, the steps followed will be the following:
|
||||
\item Check using the helper bpf\_get\_current\_comm() that we are hooking the syscall of our target program. For instance, if we are targeting the commands entered by the user in the terminal, we would look for process \textit{bash}.
|
||||
\item Backup the values of the filename and all arguments.
|
||||
\item Write using bpf\_probe\_write\_user into the filename, subtituting it with the filename of our malicious program.
|
||||
\item Check that the write call was successful, and that the values of the arguments are still the same as before (since as we explained in section \ref{subsection:sys_execve_writing}, these may be modified simultaneously). If one of these errors happened, we will write back into the filename the original program filename and exit from the tracepoint.
|
||||
\item Check that the write call was successful, and that the values of the arguments are still the same as before (since as we explained in Section \ref{subsection:sys_execve_writing}, these may be modified simultaneously). If one of these errors happened, we will write back into the filename the original program filename and exit from the tracepoint.
|
||||
\item Write using bpf\_probe\_write\_user into the first argument argv[0], substituting it with the filename of the original program.
|
||||
\item Check again that the write call was successful, and that the values of the arguments are still the same as before. If one of these errors happened, we will write back into the argv[0] the original argument, and exit from the tracepoint.
|
||||
\end{enumerate}
|
||||
@@ -658,12 +659,12 @@ As we can observe in the figure, the malicious program will create multiple sys\
|
||||
\item Firstly, the malicious program receives the arguments modified from eBPF, where the original filename has been hidden in argv[0].
|
||||
\item In order to be executed as sudo, the program crafts a new sys\_execve call for running itself as sudo. For this, it creates a sudo process, which will inspect arguments argv[1] and onwards to construct its own privileged sys\_execve call once it checks the user has sudo permissions.
|
||||
|
||||
Since our malicious program does not have sudo permissions, we make use of the privilege escalation module we explained in section \ref{section:privesc} in order to modify the contents of the \textit{/etc/sudoers} file and tricking the sudo process into considering we have sudo privilege. After this, the sudo process makes a sys\_execve call to the malicious process, which this time will be running with root permissions.
|
||||
Since our malicious program does not have sudo permissions, we make use of the privilege escalation module we explained in Section \ref{section:privesc} in order to modify the contents of the \textit{/etc/sudoers} file and tricking the sudo process into considering we have sudo privilege. After this, the sudo process makes a sys\_execve call to the malicious process, which this time will be running with root permissions.
|
||||
|
||||
\item Once the malicious program is running with root privileges, it can perform different actions in the infected machine. In our rootkit, this program (which can be found in TODO), establishes a connection with the remote rootkit client using a raw sockets-based protocol (which will be explain in section \ref{TODO}).
|
||||
\item Once the malicious program is running with root privileges, it can perform different actions in the infected machine. In our rootkit, this program (which can be found in the repository at \cite{repo_execve_hijack}), establishes a connection with the remote rootkit client using a raw sockets-based protocol (which will be explain in Section \ref{subsection:c2}).
|
||||
|
||||
Apart from this, the malicious program will now run the original program, by taking argv[1] as the filename and considering the rest of the argv[] array, starting at position 2, as the program arguments (argv[1], argv[2]...). With respect to argv[0], its original value is easily recovered from the original filename.
|
||||
%TODO link to program in repository
|
||||
|
||||
\end{enumerate}
|
||||
|
||||
|
||||
@@ -672,14 +673,14 @@ This section covers a comprehensive analysis of the design, implementation and f
|
||||
|
||||
Apart from the XDP and TC eBPF programs which compound the core of the backdoor module, we had to design and implement a series of network protocols which enable to communicate through the network with the rootkit client. Also, we will consider that a firewall, or an Intrusion Detection System (IDS) \cite{ips} may be scanning the traffic, searching for suspicious packet. Therefore, we will attempt to camouflage our traffic as common traffic generated by benign applications.
|
||||
|
||||
Note that IDSs and firewalls are usually located outside of the host, in the middle point between the router which connects to the Internet and the host. Therefore, it is not enough that we hide our rootkit packets from the kernel using XDP as we explained in section \ref{section:abusing_networking}, but rather we must aim to design packets which are not suspicious to be malicious even from the perspective of software that sits in the middle of all of our transmissions through the network.
|
||||
Note that IDSs and firewalls are usually located outside of the host, in the middle point between the router which connects to the Internet and the host. Therefore, it is not enough that we hide our rootkit packets from the kernel using XDP as we explained in Section \ref{section:abusing_networking}, but rather we must aim to design packets which are not suspicious to be malicious even from the perspective of software that sits in the middle of all of our transmissions through the network.
|
||||
|
||||
\subsection{Backdoor triggers} \label{subsection:triggers}
|
||||
After a machine is infected by the rootkit, the rootkit client program will be used by the attacker to initiate a connection with the backdoor. However, first and foremost the backdoor needs to be able to detect whether a packet corresponds to common traffic generated by the host applications, or if it is coming from the rootkit client. This is because the attacker may be launching the rootkit client from any IP address, and listening at any port, so the backdoor must learn these parameters from the rootkit client, whose identity must be "authenticated" before establishing a connection with it. The first packet or group of packets whose purpose is to instruct the backdoor about who is the rootkit client and initiate a connection is known as a "trigger".
|
||||
|
||||
Although there exist a wide variety of types of triggers, each type offers different advantages and drawbacks. In our rootkit, we have implemented multiple triggers with the purpose of discussing multiple authentication options, ranging from simple keywords inserted on packets, to complex packet streams that are based on triggers found in real-world rootkits.
|
||||
|
||||
Note that, as we introduced in section \ref{section:networking_fundamentals}, we will be exclusively working with TCP/IP packets, but an eBPF backdoor is capable of operating with any protocol of the network stack.
|
||||
Note that, as we introduced in Section \ref{section:networking_fundamentals}, we will be exclusively working with TCP/IP packets, but an eBPF backdoor is capable of operating with any protocol of the network stack.
|
||||
|
||||
\textbf{Keyword-based triggers}\\
|
||||
These triggers are one of the simplest but also the most easily detectable by any program inspecting the network traffic. This type of trigger consists of including a keyword (a simple string) inside the payload of the TCP packet. Figure \ref{fig:keyword_trigger} shows an example of a trigger of this kind.
|
||||
@@ -691,7 +692,9 @@ These triggers are one of the simplest but also the most easily detectable by an
|
||||
\label{fig:keyword_trigger}
|
||||
\end{figure}
|
||||
|
||||
Our rootkit is prepared to listen for keyword-based triggers, although it is a simple Proof of Concept (PoC) which does not take part in the main C2 functionality. In the case of the trigger shown in figure \ref{fig:keyword_trigger}, the rootkit will analyse the packet and detect that the pre-defined keyword "XDP\_PoC\_0" has been inserted into the payload, thus learning that the packet has been sent by the attacker. In the PoC implemented in our rootkit, this triggers an overwrite action, in which the XDP program will proceed to modify the payload and the packet size, changing the contents of the packet. This PoC can be seen in action in section \ref{TODO}.
|
||||
Our rootkit is prepared to listen for keyword-based triggers, although it is a simple Proof of Concept (PoC) which does not take part in the main C2 functionality. In the case of the trigger shown in Figure \ref{fig:keyword_trigger}, the rootkit will analyse the packet and detect that the pre-defined keyword "XDP\_PoC\_0" has been inserted into the payload, thus learning that the packet has been sent by the attacker. In the PoC implemented in our rootkit, this triggers an overwrite action, in which the XDP program will proceed to modify the payload and the packet size, changing the contents of the packet. This PoC can be seen in action in section \ref{subsection:poc_evaluation}.
|
||||
|
||||
Note that this functionality of XDP, although it has not been integrated in our rootkit, enables a wide range of attacks related with the network, effectively working as Man-in-the-Middle. An example of this is HTTPS Downgrade attacks, where we would tamper with the traffic related to the cipher suite negotiation process so that it turns into a plaintext HTTP connection or an HTTPS connection with a less-secure cipher suite \cite{downgrade_attack}.
|
||||
|
||||
\textbf{Port-knocking triggers}\\
|
||||
This type of triggers is based on a common previously agreed sequence of ports which both the backdoor and the client share beforehand. When the client wants to initiate a connection with the backdoor, it will send an ordered sequence of packets directed to multiple of the ports of the infected host, so that the order of these ports corresponds to the sequence agreed with the backdoor \cite{port_knocking}. A backdoor sniffing network traffic will detect this pattern and initiate a connection with the source.
|
||||
@@ -719,12 +722,12 @@ As we can observe in the figure, a series of 8 data sections of 2 bytes of lengt
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|c|>{\centering\arraybackslash}p{8cm}|}
|
||||
\hline
|
||||
Value & Action\\
|
||||
\textbf{VALUE} & \textbf{ACTION}\\
|
||||
\hline
|
||||
\hline
|
||||
0x1F29 & Request to start an encrypted pseudo-shell connection.\\
|
||||
\hline
|
||||
0x4E14 & Request to start a 'phantom shell' connection (this is explained in section \ref{TODO}).\\
|
||||
0x4E14 & Request to start a 'phantom shell' connection (this is explained in Section \ref{subsection:c2}).\\
|
||||
\hline
|
||||
0x1D25 & Request to load and attach all rootkit eBPF programs.\\
|
||||
\hline
|
||||
@@ -738,7 +741,7 @@ Value & Action\\
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|c|c|}
|
||||
\hline
|
||||
Key & Value\\
|
||||
\textbf{KEY} & \textbf{VALUE}\\
|
||||
\hline
|
||||
\hline
|
||||
K1 & 0x56A4\\
|
||||
@@ -750,7 +753,7 @@ K2 & 0x7813\\
|
||||
\label{table:k1_k2_values}
|
||||
\end{table}
|
||||
|
||||
The above format guarantees that two packets will never contain the same data, while at the same time the result is a TCP packet with random data. Therefore, when the backdoor receives any TCP packet, it will attempt to use K1, K2 and K3 to calculate the operations shown in figure \ref{fig:bvp47_trigger}. If the format matches, then it will instruct the rootkit module responsible to execute the action related to K3.
|
||||
The above format guarantees that two packets will never contain the same data, while at the same time the result is a TCP packet with random data. Therefore, when the backdoor receives any TCP packet, it will attempt to use K1, K2 and K3 to calculate the operations shown in Figure \ref{fig:bvp47_trigger}. If the format matches, then it will instruct the rootkit module responsible to execute the action related to K3.
|
||||
|
||||
Although this type of trigger is stealthier than the previous we presented, its main drawback is that, upon a forensic investigation and decompilation of the rootkit and backdoor, the value of the keys can be found and therefore its traffic detected.
|
||||
|
||||
@@ -766,7 +769,7 @@ This trigger is based on the one included on the implant called "Hive", from whi
|
||||
|
||||
In our rootkit, we will follow a similar approach, hiding a large set of data not in the payload of a TCP packet, but in the TCP headers itself. Our packets will also be marked with the SYN flag. By taking these two measures, the stream of packets would seem a harmless succession of SYN packets requesting to start a connection.
|
||||
|
||||
Firstly, the rootkit client will define the data payload to send as shown in figure \ref{fig:hive_data}.
|
||||
Firstly, the rootkit client will define the data payload to send as shown in Figure \ref{fig:hive_data}.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -775,7 +778,7 @@ Firstly, the rootkit client will define the data payload to send as shown in fig
|
||||
\label{fig:hive_data}
|
||||
\end{figure}
|
||||
|
||||
As we can observe in the figure, the rootkit will tell the backdoor information about to which IP address the rootkit has to send back a response. This enables to send the multi-packet trigger from a spoofed IP address and port. It also contains another K3 XORed with the port, so that the backdoor knows which action is requested by the rootkit client. The values for this K3 are the same as we showed in table \ref{table:k3_values}.
|
||||
As we can observe in the figure, the rootkit will tell the backdoor information about to which IP address the rootkit has to send back a response. This enables to send the multi-packet trigger from a spoofed IP address and port. It also contains another K3 XORed with the port, so that the backdoor knows which action is requested by the rootkit client. The values for this K3 are the same as we showed in Table \ref{table:k3_values}.
|
||||
|
||||
The payload also contains two particularly relevant fields, a CRC and a XOR key:
|
||||
\begin{itemize}
|
||||
@@ -787,7 +790,7 @@ A CRC is necessary because we may receive corrupted packets (TCP guarantees inte
|
||||
|
||||
After the rootkit client has built the data payload to send, it will divide it into multiple chunks and inject them into some of the fields at the TCP headers. We have implemented two different triggers according to this:
|
||||
\begin{enumerate}
|
||||
\item The first type of trigger consists of dividing the payload into 3 chunks of 4 bytes each, and injecting them into the sequence number of SYN TCP packets, as shown in figure \ref{fig:hive_seqnum}.
|
||||
\item The first type of trigger consists of dividing the payload into 3 chunks of 4 bytes each, and injecting them into the sequence number of SYN TCP packets, as shown in Figure \ref{fig:hive_seqnum}.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -796,7 +799,7 @@ After the rootkit client has built the data payload to send, it will divide it i
|
||||
\label{fig:hive_seqnum}
|
||||
\end{figure}
|
||||
|
||||
\item The second type of trigger consists of dividing the payload into 6 chunks of 2 bytes each, and injecting them into the source port of SYN TCP packets, as shown in figure \ref{fig:hive_srcport}.
|
||||
\item The second type of trigger consists of dividing the payload into 6 chunks of 2 bytes each, and injecting them into the source port of SYN TCP packets, as shown in Figure \ref{fig:hive_srcport}.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -807,7 +810,7 @@ After the rootkit client has built the data payload to send, it will divide it i
|
||||
|
||||
\end{enumerate}
|
||||
|
||||
Note that, although in figure \ref{fig:hive_seqnum} and \ref{fig:hive_srcport} the data is injected directly, this data has been transformed under the rolling XOR, so a firewall or IDS would not easily reconstruct the IP or the PORT just by looking at the packet.
|
||||
Note that, although in Figure \ref{fig:hive_seqnum} and \ref{fig:hive_srcport} the data is injected directly, this data has been transformed under the rolling XOR, so a firewall or IDS would not easily reconstruct the IP or the PORT just by looking at the packet.
|
||||
|
||||
After the rootkit client constructs the packet stream to send, the packets are sent in order to the infected system and the backdoor will have to process them. The backdoor will only be able to acknowledge that a trigger has been sent after the 3 (or the 6) packets have been received, therefore the XDP program is in charge of saving the last 3 (or the last 6) packets received from each IP address at a minimum.
|
||||
|
||||
@@ -831,13 +834,13 @@ If the previous checks do not fail, it means the packet stream was a multi-strea
|
||||
|
||||
%TODO INTRODUCE IMAGES OF SHELLS
|
||||
\subsection{Command and Control} \label{subsection:c2}
|
||||
This section details the C2 capabilities incorporated in our rootkit, that is, mechanisms that enable the attacker to introduce rootkit commands (not to be confused with Linux commands in a shell) from the remote rootkit client and to be executed in the infected machine, returning the output of the command (if any) back to the client. These rootkit commands can be instructed by sending a backdoor trigger, which as we mentioned, depending on the value of K3 in the trigger, a different rootkit action will be executed by the backdoor (available values are displayed in table \ref{table:k3_values}).
|
||||
This section details the C2 capabilities incorporated in our rootkit, that is, mechanisms that enable the attacker to introduce rootkit commands (not to be confused with Linux commands in a shell) from the remote rootkit client and to be executed in the infected machine, returning the output of the command (if any) back to the client. These rootkit commands can be instructed by sending a backdoor trigger, which as we mentioned, depending on the value of K3 in the trigger, a different rootkit action will be executed by the backdoor (available values are displayed in Table \ref{table:k3_values}).
|
||||
|
||||
Some of the actions triggered by the backdoor involve modifying the behaviour of the rootkit (such as attaching/detaching eBPF programs remotely), while others enable the attacker to spawn rootkit 'pseudo-shells'. These pseudo-shells are a special rootkit-to-´rootkit client connections which simulate a shell program, enabling the attacker to execute Linux commands remotely and get the results as if it was executing them directly in the infected machine. During this connection, the rootkit and the rootkit client will exchange messages containing commands and information. For this, both programs need to agree on a common protocol which is mutually understood, defining the format and content of these transmissions.
|
||||
Some of the actions triggered by the backdoor involve modifying the behaviour of the rootkit (such as attaching/detaching eBPF programs remotely), while others enable the attacker to spawn rootkit 'pseudo-shells'. These pseudo-shells are a special rootkit-to-rootkit client connections which simulate a shell program, enabling the attacker to execute Linux commands remotely and get the results as if it was executing them directly in the infected machine. During this connection, the rootkit and the rootkit client will exchange messages containing commands and information. For this, both programs need to agree on a common protocol which is mutually understood, defining the format and content of these transmissions.
|
||||
|
||||
Apart from being able to spawn pseudo-shells by sending such action requests to the backdoor using a backdoor trigger, some other shells can also be spawned as a result of a successful exploitation of either the library injection module or the execution hijacking module. In particular, the malicious library we injected in section \ref{section:lib_injection} and the malicious user program of section \ref{section:execution_hijack} spawn one of these shells once they are executed.
|
||||
Apart from being able to spawn pseudo-shells by sending such action requests to the backdoor using a backdoor trigger, some other shells can also be spawned as a result of a successful exploitation of either the library injection module or the execution hijacking module. In particular, the malicious library we injected in Section \ref{section:lib_injection} and the malicious user program of Section \ref{section:execution_hijack} spawn one of these shells once they are executed.
|
||||
|
||||
As a summary, figure \ref{fig:c2_summ} shows an overview of C2 infrastructure.
|
||||
As a summary, Figure \ref{fig:c2_summ_infra} shows an overview of C2 infrastructure.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -852,7 +855,7 @@ We will now proceed to analyse each of these connections and shell-like mechanis
|
||||
|
||||
|
||||
\textbf{Reverse shell}\\
|
||||
This is the simplest and most automated shell we can obtain from an infected machine. This shell is spawned when we inject the malicious library of the library injection module (section \ref{section:lib_injection}), therefore the parties involved in the transmission are the rootkit client and the malicious library.
|
||||
This is the simplest and most automated shell we can obtain from an infected machine. This shell is spawned when we inject the malicious library of the library injection module (Section \ref{section:lib_injection}), therefore the parties involved in the transmission are the rootkit client and the malicious library.
|
||||
|
||||
A reverse shell is initiated from the infected machine to the attacker, that is, the malicious library actively initiates the connection, and the rootkit client must listen for this request using a netcat listener (or a similar program). A reverse shell is usually created in three steps:
|
||||
\begin{enumerate}
|
||||
@@ -870,7 +873,7 @@ The attacker, for its part, can accept the TCP connection requested by the infec
|
||||
\textbf{Plaintext pseudo-shell}\\
|
||||
This shell-like connection enables the attacker to send commands, execute them in the infected machine and receive back the output without the execution of any shell program, and with all transmissions being sent in plaintext over the network.
|
||||
|
||||
This type of shell is obtained by running the malicious program of the execution hijacking module of the rootkit. The rootkit currently does not incorporate a backdoor trigger that launches this module, but rather it is started automatically once the malicious program is executed (see table \ref{table:k3_values}, we have not included a K3 for running an unencrypted pseudo-shell).
|
||||
This type of shell is obtained by running the malicious program of the execution hijacking module of the rootkit. The rootkit currently does not incorporate a backdoor trigger that launches this module, but rather it is started automatically once the malicious program is executed (see Table \ref{table:k3_values}, we have not included a K3 for running an unencrypted pseudo-shell).
|
||||
|
||||
While running a plaintext pseudo-shell, the rootkit client and the malicious program from the execution hijacking module (hereafter called the rootkit, since it is part of it) will make use of a master/slave protocol where the rootkit client acts as the master (sending commands) and the rootkit acts as the slave (it only sends data in response of a client message). On each transmission, the rootkit client will send a single TCP packet (without a preceding 3-way handshake) in which the command is embedded as the payload. The rootkit will execute this command and answer back with the output in another single TCP packet.
|
||||
|
||||
@@ -886,7 +889,7 @@ Apart from the data being transmitted (the command and the output of that comman
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|c|>{\centering\arraybackslash}p{8cm}|}
|
||||
\hline
|
||||
\textbf{Header} & \textbf{Description}\\
|
||||
\textbf{HEADER} & \textbf{DESCRIPTION}\\
|
||||
\hline
|
||||
\hline
|
||||
CC\_SYN & Sent by the rootkit client to the rootkit, requests to initiate a connection. Expects a packet with CC\_ACK in response.\\
|
||||
@@ -913,7 +916,7 @@ Figure \ref{fig:ups_transmission} illustrates a common transmission following th
|
||||
\label{fig:ups_transmission}
|
||||
\end{figure}
|
||||
|
||||
As we can observe in figure \ref{fig:ups_transmission}, packets containing CC\_SYN and CC\_ACK act as a custom 2-way handshake. This step could be considered redundant and has been included only to share a resemblance with the TCP protocol.
|
||||
As we can observe in Figure \ref{fig:ups_transmission}, packets containing CC\_SYN and CC\_ACK act as a custom 2-way handshake. This step could be considered redundant and has been included only to share a resemblance with the TCP protocol.
|
||||
|
||||
Also, note that after a successful CC\_SYN-CC\_ACK exchange there is no need to repeat it after a CC\_MSG, the transmission will consist of consecutive CC\_MSG packets until the pseudo-shell is closed from the rootkit client with a CC\_FIN.
|
||||
|
||||
@@ -921,14 +924,14 @@ Also, note that after a successful CC\_SYN-CC\_ACK exchange there is no need to
|
||||
\textbf{Encrypted pseudo-shell}\\
|
||||
Similarly to plaintext pseudo-shells, encrypted pseudo-shells enable the attacker to send commands, execute them in the infected machine and receive back the output, but all transmissions will be contained in the context of a secure encrypted connection using TLS.
|
||||
|
||||
In our rootkit, this type of shells are spawned after the rootkit client requests such an action to the network backdoor by setting the appropriate value of K3 (see table \ref{table:k3_values}) on either a pattern-based backdoor trigger or a multi-packet trigger. Once such a trigger is received in the backdoor, it will request to the rootkit user process to execute a TLS client that connects to the TLS server run at the rootkit client.
|
||||
In our rootkit, this type of shells are spawned after the rootkit client requests such an action to the network backdoor by setting the appropriate value of K3 (see Table \ref{table:k3_values}) on either a pattern-based backdoor trigger or a multi-packet trigger. Once such a trigger is received in the backdoor, it will request to the rootkit user process to execute a TLS client that connects to the TLS server run at the rootkit client.
|
||||
|
||||
Once both parties are connected using TLS, they exchange data using a custom protocol, similar to the one used for plaintext pseudo-shells, but this time using TLS-contained messages. This message exchange works as master/slave protocol too, where the rootkit client will send a command to the rootkit, and the rootkit will execute the command and answer back with the output. Similarly to plaintext pseudo-shells, these messages are composed of a header and the data being transmitted. Table \ref{table:eps_headers} show the headers according to the protocol.
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|c|>{\centering\arraybackslash}p{8cm}|}
|
||||
\hline
|
||||
\textbf{Header} & \textbf{Description}\\
|
||||
\textbf{HEADER} & \textbf{DESCRIPTION}\\
|
||||
\hline
|
||||
\hline
|
||||
CC\_COMM\_RQ\# & Sent by the rootkit client to the rootkit, sends a command to be executed.\\
|
||||
@@ -949,9 +952,9 @@ As we can observe, this protocol works similarly to the one in pseudo-shells, wi
|
||||
|
||||
|
||||
\textbf{Phantom shell}\\
|
||||
This shell-like connection works with the coordination of both the XDP and TC modules at the backdoor. It does not involve sending any packets from the user space, but rather the backdoor will reuse packets being sent by other applications in the infected machine, modifying them so that they are directed to the rootkit client. Afterwards, the original packet will be transmitted without modifications to its original destination due to the TCP retransmissions. This technique has been explained in detail in section \ref{subsection:network_attacks}.
|
||||
This shell-like connection works with the coordination of both the XDP and TC modules at the backdoor. It does not involve sending any packets from the user space, but rather the backdoor will reuse packets being sent by other applications in the infected machine, modifying them so that they are directed to the rootkit client. Afterwards, the original packet will be transmitted without modifications to its original destination due to the TCP retransmissions. This technique has been explained in detail in Section \ref{subsection:network_attacks}.
|
||||
|
||||
A phantom shell can be obtained from the rootkit client by sending a backdoor trigger (only pattern-based triggers are supported for this shell) with the corresponding value of K3 (see table \ref{table:k3_values}). The XDP program at the backdoor receives the trigger and communicates to the TC program that the backdoor has been instructed to start a phantom shell. TC will modify a single packet and send it to the rootkit client, indicating that the backdoor is ready to start the phantom shell. After that, the client and the backdoor exchange TCP packets using a shared protocol (similar to that of plaintext pseudo-shells) in the following manner:
|
||||
A phantom shell can be obtained from the rootkit client by sending a backdoor trigger (only pattern-based triggers are supported for this shell) with the corresponding value of K3 (see Table \ref{table:k3_values}). The XDP program at the backdoor receives the trigger and communicates to the TC program that the backdoor has been instructed to start a phantom shell. TC will modify a single packet and send it to the rootkit client, indicating that the backdoor is ready to start the phantom shell. After that, the client and the backdoor exchange TCP packets using a shared protocol (similar to that of plaintext pseudo-shells) in the following manner:
|
||||
\begin{enumerate}
|
||||
\item The rootkit client sends a TCP packet with the command to execute.
|
||||
\item The XDP program at the backdoor scans the traffic and detects that a TCP packet belonging to a phantom shell has been received (recognizing it by its header at the TCP payload).
|
||||
@@ -967,7 +970,7 @@ Both XDP and the user space rootkit program will communicate with the TC program
|
||||
\item The command requested by the rootkit client (this is empty when XDP communicates having received the backdoor trigger in the first step).
|
||||
\end{itemize}
|
||||
|
||||
With respect to the protocol being used, the TCP packets exchanged between the rootkit client and the TC program is the same as that shown in figure \ref{fig:ups_packet_struct}. The only difference is in the headers being used, which are described in table \ref{table:phantom_headers}.
|
||||
With respect to the protocol being used, the TCP packets exchanged between the rootkit client and the TC program is the same as that shown in Figure \ref{fig:ups_packet_struct}. The only difference is in the headers being used, which are described in Table \ref{table:phantom_headers}.
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|c|>{\centering\arraybackslash}p{8cm}|}
|
||||
@@ -1005,11 +1008,11 @@ Note that, currently, the rootkit only hijacks TCP packets, but it could also mo
|
||||
|
||||
|
||||
\textbf{Backdoor commands}\\
|
||||
Apart from supporting the remote execution of commands via the shell-like connections we have covered in this section, the backdoor also enables two other backdoor commands which modify the behaviour of the rootkit. As we can observe in table \ref{table:k3_values}, these commands consist on enabling or disabling eBPF programs remotely.
|
||||
Apart from supporting the remote execution of commands via the shell-like connections we have covered in this section, the backdoor also enables two other backdoor commands which modify the behaviour of the rootkit. As we can observe in Table \ref{table:k3_values}, these commands consist on enabling or disabling eBPF programs remotely.
|
||||
|
||||
These commands are launched from the rootkit client and get sent to the backdoor in the form of either a pattern-based trigger or any of the two forms of multi-packet trigger. As with any other backdoor trigger, the XDP program checks the value of K3 contained in the trigger and issues the corresponding action.
|
||||
|
||||
In the case of these commands, the order needs to be transmitted to the rootkit user space program via the ring buffer, from where the eBPF programs will be attached or detached using the eBPF program configurator. We will cover the eBPF program configurator extensively in section \ref{subsection:ebpf_progs_config}.
|
||||
In the case of these commands, the order needs to be transmitted to the rootkit user space program via the ring buffer, from where the eBPF programs will be attached or detached using the eBPF program configurator. We will cover the eBPF program configurator extensively in Section \ref{subsection:ebpf_progs_config}.
|
||||
|
||||
Apart from just demonstrating the C2 capabilities of the rootkit, these commands are useful to perform a soft reset of the rootkit remotely (since it reloads all eBPF programs with the exception of the backdoor) or to minimize the rootkit activity to the minimum.
|
||||
|
||||
@@ -1031,11 +1034,11 @@ As we can observe in the figure, the XDP program must be attached to a network i
|
||||
|
||||
For any packet received, a filtering routine will be applied, whose purpose is to discard any packet the backdoor will not work with, only keeping TCP/IP packets. Moreover, these initial checks done with the purpose of determining the protocol must always been made, otherwise the eBPF verifier may consider any access to the packet as invalid (since it will not be sure about the type and bounds of the fields it is accessing). We can also appreciate that the XDP program filters according to the destination port. The reason is that we have designed our backdoor trigger so that they are always directed to this port number.
|
||||
|
||||
After the initial filtering routine, the XDP program will check for any of the triggers or headers it could be received to support the C2 capabilities of the backdoor. For this, more filters will be implemented, usually checking for the payload or packet size first, and later checking for the actual contents since the verifier forbids accessing payload data if its length is not assured. Also, in the case of working with multi-packet triggers, the related eBPF maps must be updated with the log of the latest packets received, as we described in section \ref{subsection:triggers}.
|
||||
After the initial filtering routine, the XDP program will check for any of the triggers or headers it could be received to support the C2 capabilities of the backdoor. For this, more filters will be implemented, usually checking for the payload or packet size first, and later checking for the actual contents since the verifier forbids accessing payload data if its length is not assured. Also, in the case of working with multi-packet triggers, the related eBPF maps must be updated with the log of the latest packets received, as we described in Section \ref{subsection:triggers}.
|
||||
|
||||
Once the type of trigger is detected, XDP proceeds to perform the actions related to the value of K3 found inside each trigger. As we described in section \ref{subsection:c2}, these include writing into the ring buffer or communicating with the TC program via the shared eBPF map.
|
||||
Once the type of trigger is detected, XDP proceeds to perform the actions related to the value of K3 found inside each trigger. As we described in Section \ref{subsection:c2}, these include writing into the ring buffer or communicating with the TC program via the shared eBPF map.
|
||||
|
||||
Note that in this diagram it has been omitted the section related with modifying incoming packets, used for the PoC shown in section \ref{TODO}. The reason is that its functionality is identical to that being shown in figure \ref{fig:c2_summ_tc} implemented by the TC program.
|
||||
Note that in this diagram it has been omitted the section related with modifying incoming packets, used for the PoC shown in Section \ref{subsection:poc_evaluation}. The reason is that its functionality is identical to that being shown in Figure \ref{fig:c2_summ_tc} implemented by the TC program.
|
||||
|
||||
|
||||
\textbf{TC}\\
|
||||
@@ -1051,7 +1054,7 @@ The TC egress program is responsible for sniffing outgoing network traffic and m
|
||||
As we can observe in the figure, the TC program will ignore any packet until some data arrives at the shared eBPF map. At that point, it will proceed to overwrite the packet with the data it has been sent by the XDP or rootkit user process. In particular, it must redirect the destination of the original packet (thus changing the IP address and destination port) and modify the payload of the packet. Therefore, it approaches the packet modification in two steps:
|
||||
\begin{itemize}
|
||||
\item Modifying the IP and TCP headers of the packet with the new destination data.
|
||||
\item Modifying the payload. Most of the times, this payload will be of different length compared to that of the original TCP packet, and therefore the TC program must modify the packet bounds. This is done using the bpf\_skb\_change\_tail helper, which we covered in section \ref{subsection:tc}. Note that, once we modify the packet bounds, the eBPF verifier will no longer trust our original checks with respect to the packet protocol and the validity of the payload. Therefore, all checks must be repeated before being able to overwrite the payload of the packet.
|
||||
\item Modifying the payload. Most of the times, this payload will be of different length compared to that of the original TCP packet, and therefore the TC program must modify the packet bounds. This is done using the bpf\_skb\_change\_tail() helper, which we covered in Section \ref{subsection:tc}. Note that, once we modify the packet bounds, the eBPF verifier will no longer trust our original checks with respect to the packet protocol and the validity of the payload. Therefore, all checks must be repeated before being able to overwrite the payload of the packet.
|
||||
\end{itemize}
|
||||
|
||||
After the requested modifications are made, the TC program passes the packet to the next layer in the kernel.
|
||||
@@ -1071,7 +1074,7 @@ The rootkit client is compiled to a single executable named \textit{injector}. T
|
||||
\label{fig:client_help}
|
||||
\end{figure}
|
||||
|
||||
As we can observe in the figure, the rootkit client enables to execute the C2 actions we have described in section \ref{subsection:c2}. Upon running any of these options, the client will first request the network interface to use. This enables the attacker to choose the specific network to which it can connect to the infected machine.
|
||||
As we can observe in the figure, the rootkit client enables to execute the C2 actions we have described in Section \ref{subsection:c2}. Upon running any of these options, the client will first request the network interface to use. This enables the attacker to choose the specific network to which it can connect to the infected machine.
|
||||
|
||||
After choosing an interface, the rootkit client crafts the respective backdoor trigger and sends it to the infected machine (we have also included an additional non-C2 PoC showing how the rootkit modifies incoming packets). Every option requires to specify the infected machine location by indicating its IP address.
|
||||
|
||||
@@ -1084,7 +1087,7 @@ After sending a backdoor trigger, the client will enter a listening state, waiti
|
||||
\label{fig:enc_shell}
|
||||
\end{figure}
|
||||
|
||||
Once the command prompt appears, the attacker may introduce commands to be executed in the infected machine. Commands may only be introduced one at a time, since the client waits for the rootkit response before showing another command prompt. When the attacker finishes using the shell, it is recommended to close the connection gracefully. For this, the client supports "global commands", a special type of command which, when introduced in the shell, does not get sent as a command to the rootkit but instead it triggers an action locally or remotely. Currently, although the infrastructure for supporting a large list of global commands has been developed, only one has been included. The attacker may introduce "EXIT" to close the connection gracefully (see in \ref{subsection:c2}, that packets for closing the connection are sent according to the protocol). Figure \ref{fig:enc_shell_comm_ex} shows the execution of multiple commands and closing the connection.
|
||||
Once the command prompt appears, the attacker may introduce commands to be executed in the infected machine. Commands may only be introduced one at a time, since the client waits for the rootkit response before showing another command prompt. When the attacker finishes using the shell, it is recommended to close the connection gracefully. For this, the client supports "global commands", a special type of command which, when introduced in the shell, does not get sent as a command to the rootkit but instead it triggers an action locally or remotely. Currently, although the infrastructure for supporting a large list of global commands has been developed, only one has been included. The attacker may introduce "EXIT" to close the connection gracefully (see in Section \ref{subsection:c2}, that packets for closing the connection are sent according to the protocol). Figure \ref{fig:enc_shell_comm_ex} shows the execution of multiple commands and closing the connection.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -1093,7 +1096,7 @@ Once the command prompt appears, the attacker may introduce commands to be execu
|
||||
\label{fig:enc_shell_comm_ex}
|
||||
\end{figure}
|
||||
|
||||
As we can observe in figures \ref{fig:enc_shell} and \ref{fig:enc_shell_comm_ex}, the client also introduces multiple messages which provide additional information to the attacker about the state of the rootkit, the client and the ongoing connection. The existing message types are INFO, SUCCESS, WARN and ERROR.
|
||||
As we can observe in Figure \ref{fig:enc_shell} and \ref{fig:enc_shell_comm_ex}, the client also introduces multiple messages which provide additional information to the attacker about the state of the rootkit, the client and the ongoing connection. The existing message types are INFO, SUCCESS, WARN and ERROR.
|
||||
|
||||
Also, note that the rootkit client needs to be executed as root, since the library RawTCP\_Lib it uses requires privileges for some of its functionalities.
|
||||
|
||||
@@ -1112,7 +1115,7 @@ Only by using RawTCP\_Lib, the rootkit client can craft backdoor triggers whose
|
||||
|
||||
Apart from this, since raw sockets are indicated for reimplementing network protocols in the user space, it allows us to avoid undesired additional traffic in our rootkit transmissions. For instance, we do not need a 3-way handshake preceding any of our transmissions.
|
||||
|
||||
Finally, the sniffing capabilities of this library are responsible of capturing the responses of the rootkit from the rootkit client. If we observe tables \ref{table:ups_headers}, \ref{table:eps_headers} and \ref{table:phantom_headers}, we can appreciate that the headers start at a common prefix "CC". This is used by the rootkit to sniff the network and capture any packet whose payload starts with that pattern.
|
||||
Finally, the sniffing capabilities of this library are responsible of capturing the responses of the rootkit from the rootkit client. If we observe Table \ref{table:ups_headers}, \ref{table:eps_headers} and \ref{table:phantom_headers}, we can appreciate that the headers start at a common prefix "CC". This is used by the rootkit to sniff the network and capture any packet whose payload starts with that pattern.
|
||||
|
||||
|
||||
|
||||
@@ -1126,14 +1129,14 @@ The user space rootkit program communicates with the other components of the roo
|
||||
\item Other eBPF maps, on which the user program can write from the user space, thus enabling user to kernel communication.
|
||||
\end{itemize}
|
||||
|
||||
In particular, the backdoor will be the responsible of most of the data written at the ring buffer, using it to request the actions corresponding to the commands received through the network (although the library injection module uses it too, see figure \ref{fig:flow_lib_injection_compact}.
|
||||
In particular, the backdoor will be the responsible of most of the data written at the ring buffer, using it to request the actions corresponding to the commands received through the network (although the library injection module uses it too, see Figure \ref{fig:flow_lib_injection_compact}.
|
||||
|
||||
Any data written into the ring buffer is encapsulated in an "event", embodied by a struct \textit{rb\_event}. This struct supports all types data that any program using the ring buffer will need (thus not all of them are filled). In order to let the user program know which fields will need to be read for a given event, each \textit{rb\_event} is marked with an attribute \textit{event\_type}, which denotes the type of data that has been written in the buffer, and an attribute \textit{code}, that further distinguishes events from the same type into their purpose. Table \ref{table:ring_buf_events} shows the event types and codes recognized by the user program:
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|c|c|>{\centering\arraybackslash}p{8cm}|}
|
||||
\hline
|
||||
\textbf{Event type} & \textbf{Code} & \textbf{Action requested} \\
|
||||
\textbf{EVENT TYPE} & \textbf{CODE} & \textbf{ACTION REQUESTED} \\
|
||||
\hline
|
||||
\hline
|
||||
INFO (0) & Any & Informative message, not requesting an action.\\
|
||||
@@ -1164,7 +1167,7 @@ During the development of the rootkit, it has been our priority to aim for the g
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|c|>{\centering\arraybackslash}p{8cm}|}
|
||||
\hline
|
||||
\textbf{File} & \textbf{Module} \\
|
||||
\textbf{FILE} & \textbf{MODULE} \\
|
||||
\hline
|
||||
\hline
|
||||
fs\_module & Contains programs related to reading and writing files, such as the privilege escalation module.\\
|
||||
@@ -1181,7 +1184,7 @@ xdp\_module & Contains programs related to the backdoor functionality.\\
|
||||
\end{table}
|
||||
|
||||
In order to load and attach eBPF programs with different parameters and
|
||||
to enable managing them at runtime, the user space program uses the eBPF program configurator. This configurator consists of two configuration structs and an API that allows for manipulating the eBPF programs state dynamically. Code snippets \ref{code:configurator_modules} and \ref{code_configurator_modules_attr} show these two structures.
|
||||
to enable managing them at runtime, the user space program uses the eBPF program configurator. This configurator consists of two configuration structs and an API that allows for manipulating the eBPF programs state dynamically. Code snippets \ref{code:configurator_modules} and \ref{code:configurator_modules_attr} show these two structures.
|
||||
|
||||
\begin{lstlisting}[language=C, caption={Program configurator struct with list of modules.}, label={code:configurator_modules}]
|
||||
module_config_t module_config = {
|
||||
@@ -1229,7 +1232,7 @@ The user space rootkit program can modify any of the struct values following a r
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|c|>{\centering\arraybackslash}p{10cm}|}
|
||||
\hline
|
||||
\textbf{Function} & \textbf{Description} \\
|
||||
\textbf{FUNCTION} & \textbf{DESCRIPTION} \\
|
||||
\hline
|
||||
\hline
|
||||
unhook\_all\_modules() & Detaches all eBPF programs.\\
|
||||
@@ -1250,7 +1253,7 @@ Therefore, the user space rootkit program will need to follow the next steps for
|
||||
\end{itemize}
|
||||
|
||||
\section{Rootkit persistence} \label{section:persistence}
|
||||
As we introduced in section \ref{section:motivation}, one of the key features of a rootkit is its persistence, aiming to maintain the infection for the longest period of time possible, including getting through shutdown events. Initially, when the machine is rebooted, all our eBPF programs will be unloaded from the kernel, and the user space rootkit program will be killed. Moreover, even if they could be run again automatically, they would no longer dispose of the root privileges needed for attaching the eBPF programs again. Therefore, the rootkit persistence module aims to tackle these two challenges:
|
||||
As we introduced in Section \ref{section:motivation}, one of the key features of a rootkit is its persistence, aiming to maintain the infection for the longest period of time possible, including getting through shutdown events. Initially, when the machine is rebooted, all our eBPF programs will be unloaded from the kernel, and the user space rootkit program will be killed. Moreover, even if they could be run again automatically, they would no longer dispose of the root privileges needed for attaching the eBPF programs again. Therefore, the rootkit persistence module aims to tackle these two challenges:
|
||||
\begin{itemize}
|
||||
\item Execute the rootkit automatically and without user interaction after a machine reboot event.
|
||||
\item Once the rootkit has acquired root privileges the first time it is executed in the machine, it must keep them including after a reboot.
|
||||
@@ -1263,7 +1266,7 @@ The cron system is made up of two main components. On one hand, the cron service
|
||||
|
||||
On the other hand, the jobs that cron will run (cron jobs) must be specified on either the \textit{/etc/crontab} file, or in files inside the \textit{/etc/cron.d} directory, written in a special cron format.
|
||||
|
||||
In our rootkit, we will specify the rootkit cron jobs in a file named \textit{/etc/cron.d/ebpfbackdoor}. This file is created and written by the script \textit{deployer.sh} which, as we mentioned in section \ref{section:rootkit_arch}, is an script to be run by the attacker to automatize the process of infecting the machine. Snippet \ref{code:deployersh} shows the content of the \textit{deployer.sh} script.
|
||||
In our rootkit, we will specify the rootkit cron jobs in a file named \textit{/etc/cron.d/ebpfbackdoor}. This file is created and written by the script \textit{deployer.sh} which, as we mentioned in Section \ref{section:rootkit_arch}, is an script to be run by the attacker to automatize the process of infecting the machine. Code snippet \ref{code:deployersh} shows the content of the \textit{deployer.sh} script.
|
||||
|
||||
\begin{lstlisting}[language=C, caption={Script deployer.sh.}, label={code:deployersh}]
|
||||
## Persistence
|
||||
@@ -1288,12 +1291,12 @@ fi
|
||||
|
||||
As we can observe in its contents, the script will take care of the installation process of the rootkit. For this, it will first check whether there exists any XDP program loaded. If there is any, it is assumed that it belongs to the rootkit backdoor and thus the process is halted. Otherwise, the rootkit is installed:
|
||||
\begin{itemize}
|
||||
\item We remove any previous existing qdisc, followed by creating the new qdisc for the TC program, which is created and attached to network interface enp0s3. This step was explained in section \ref{subsection:tc}.
|
||||
\item We remove any previous existing qdisc, followed by creating the new qdisc for the TC program, which is created and attached to network interface enp0s3. This step was explained in Section \ref{subsection:tc}.
|
||||
\item We attach the TC program to the newly created qdisc.
|
||||
\item We execute the main file (\textit{kit}) of the rootkit, specifying the network address for the XDP program to use. This will launch the user space rootkit program, which will load and attach the eBPF programs in the kernel.
|
||||
\end{itemize}
|
||||
|
||||
Also, as we mentioned, the \textit{deployer.sh} script takes care of the rootkit persistence by writing an entry into the file \textit{/etc/cron.d/ebpfbackdoor}. Snippet \ref{code:bpfbackdoor_cron} shows the outcome of the data written into this file.
|
||||
Also, as we mentioned, the \textit{deployer.sh} script takes care of the rootkit persistence by writing an entry into the file \textit{/etc/cron.d/ebpfbackdoor}. Code snippet \ref{code:bpfbackdoor_cron} shows the outcome of the data written into this file.
|
||||
|
||||
\begin{lstlisting}[language=C, caption={Content of /etc/cron.d/ebpfbackdoor.}, label={code:bpfbackdoor_cron}]
|
||||
* * * * * osboxes /bin/sudo /home/osboxes/TFG/apps/deployer.sh
|
||||
@@ -1320,7 +1323,7 @@ Considering the above, we can see that, after a machine reboot event, the cron d
|
||||
\subsection{Preserving privileges}
|
||||
As we mentioned in the previous section, the \textit{deployer.sh} script will need to be executed as sudo, since it needs root privileges for installing the rootkit. However, after a reboot, the privilege escalation module of the rootkit will not be installed yet, and therefore the script needs some other way of achieving the needed permissions.
|
||||
|
||||
For this, as we can observe in snippet \ref{code:deployersh}, the \textit{deployer.sh} script will write a sudo entry in the sudoers.d directory, in a new file \textit{/etc/sudoers.d/ebpfbackdoor}. This directory is used by the sudo system in conjunction of the \textit{/etc/sudoers} file we described in section \ref{subsection:sudoers_file}, so that the rootkit can keep its original root privileges after a system reboot. The entry that will be written into the file is identical to that we introduced in hijacked read accesses to the \textit{/etc/sudoers} file.
|
||||
For this, as we can observe in Code snippet \ref{code:deployersh}, the \textit{deployer.sh} script will write a sudo entry in the sudoers.d directory, in a new file \textit{/etc/sudoers.d/ebpfbackdoor}. This directory is used by the sudo system in conjunction of the \textit{/etc/sudoers} file we described in Section \ref{subsection:sudoers_file}, so that the rootkit can keep its original root privileges after a system reboot. The entry that will be written into the file is identical to that we introduced in hijacked read accesses to the \textit{/etc/sudoers} file.
|
||||
|
||||
Therefore, after a reboot, the cron daemon will run the \textit{deployer.sh} script with sudo. The sudo process will find that it has sudo privileges, and thus it will be executed as root.
|
||||
|
||||
@@ -1346,7 +1349,7 @@ As we can observe in the figures, the initial execution permission and root priv
|
||||
|
||||
|
||||
\section{Rootkit stealth} \label{section:rootkti_stealth}
|
||||
In section \ref{section:persistence}, we presented the mechanisms used by the rootkit to persist the infection of the machine after a reboot event. However, since it is based on creating additional files, they may get eventually found by the system owner or by some software tool, so there exists a risk on leaving them in the system. Additionally, the rootkit files will need to be stored at some location, in which they may get discovered.
|
||||
In Section \ref{section:persistence}, we presented the mechanisms used by the rootkit to persist the infection of the machine after a reboot event. However, since it is based on creating additional files, they may get eventually found by the system owner or by some software tool, so there exists a risk on leaving them in the system. Additionally, the rootkit files will need to be stored at some location, in which they may get discovered.
|
||||
|
||||
Therefore, it is in our interest to prevent the user from accessing any of the files belonging to the rootkit, either the executables or the files for persistence. Because of this reason, we will attempt to achieve two goals:
|
||||
\begin{itemize}
|
||||
@@ -1355,14 +1358,14 @@ Therefore, it is in our interest to prevent the user from accessing any of the f
|
||||
\end{itemize}
|
||||
|
||||
\subsection{Reading directories in Linux}
|
||||
The system call responsible of reading the files and subdirectories in a directory is sys\_getdents64() \cite{code_kernel_getdents64}. This system call reads the entries from a directory (files, subdirectories, links) and writes them as an array in a user space buffer so that the user program can iterate over it. Each of the entries are formatted as a linux\_dirent64 struct \cite{getdents_man} \cite{code_kernel_linux_dirent64}.
|
||||
The system call responsible of reading the files and subdirectories in a directory is sys\_getdents64 \cite{code_kernel_getdents64}. This system call reads the entries from a directory (files, subdirectories, links) and writes them as an array in a user space buffer so that the user program can iterate over it. Each of the entries are formatted as a linux\_dirent64 struct \cite{getdents_man} \cite{code_kernel_linux_dirent64}.
|
||||
|
||||
The arguments of the sys\_getdents64 syscall are listed in table \ref{table:getdents_args}. The linux\_dirent64 format is shown in table \ref{table:linux_dirent64}.
|
||||
The arguments of the sys\_getdents64 syscall are listed in Table \ref{table:getdents_args}. The linux\_dirent64 format is shown in Table \ref{table:linux_dirent64}.
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|c|>{\centering\arraybackslash}p{7cm}|}
|
||||
\hline
|
||||
\textbf{Argument} & \textbf{Description} \\
|
||||
\textbf{ARGUMENT} & \textbf{DESCRIPTION} \\
|
||||
\hline
|
||||
\hline
|
||||
unsigned int fd & File descriptor of the directory to read.\\
|
||||
@@ -1381,7 +1384,7 @@ long <Return value> & Returns total number of bytes read by the system call.\\
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|c|>{\centering\arraybackslash}p{7cm}|}
|
||||
\hline
|
||||
\textbf{Argument} & \textbf{Description} \\
|
||||
\textbf{ARGUMENT} & \textbf{DESCRIPTION} \\
|
||||
\hline
|
||||
\hline
|
||||
u64 d\_ino & Inode number of the file\\
|
||||
@@ -1399,7 +1402,7 @@ char d\_name[] & Filename\\
|
||||
\label{table:linux_dirent64}
|
||||
\end{table}
|
||||
|
||||
As we can observe in table \ref{table:getdents_args}, sys\_getdents64 receives a linux\_dirent64 *dirent argument pointing to a buffer in the user space (it is marked as \_\_user). This buffer is not of length linux\_dirent64, but rather consists of an array of these structs. Moreover, the size of a linux\_dirent64 struct is variable (specifically, the attribute d\_name[] is variable, since the name of a file or a directory is not fixed). In turn, the attribute d\_type indicates the length of each linux\_dirent64, so that the user program can know the length of the entry and iterate over the buffer. Additionally, as indicated in table \ref{table:getdents_args}, the sys\_getdents64 syscall returns the summatory of the length of all the linux\_dirent64 entries in the array, so that the user program can know which is the final entry in the buffer. Figure \ref{fig:getdents_summ} summarizes this process, illustrating how a user program iterates over the buffer written by the sys\_getdents64 syscall.
|
||||
As we can observe in Table \ref{table:getdents_args}, sys\_getdents64 receives a linux\_dirent64 *dirent argument pointing to a buffer in the user space (it is marked as \_\_user). This buffer is not of length linux\_dirent64, but rather consists of an array of these structs. Moreover, the size of a linux\_dirent64 struct is variable (specifically, the attribute d\_name[] is variable, since the name of a file or a directory is not fixed). In turn, the attribute d\_type indicates the length of each linux\_dirent64, so that the user program can know the length of the entry and iterate over the buffer. Additionally, as indicated in Table \ref{table:getdents_args}, the sys\_getdents64 syscall returns the summatory of the length of all the linux\_dirent64 entries in the array, so that the user program can know which is the final entry in the buffer. Figure \ref{fig:getdents_summ} summarizes this process, illustrating how a user program iterates over the buffer written by the sys\_getdents64 syscall.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -1411,11 +1414,11 @@ As we can observe in table \ref{table:getdents_args}, sys\_getdents64 receives a
|
||||
As we can observe in the figure, each linux\_dirent64 struct has a different length, however they are positioned aligned in the buffer with respect to a multiple of 4 \cite{code_kerel_getdents_buffer_alignation}. Then, using the d\_reclen attribute, the user program can iterate over each of the linux\_dirent64 structs, until it reaches a buffer offset equal to that incated as a return value of the sys\_getdents64 syscall.
|
||||
|
||||
\subsection{Hijacking sys\_getdents64}
|
||||
As we indicated in table \ref{table:getdents_args}, the \textit{dirent} argument in sys\_getdents64 is a pointer to a user space buffer, and therefore an eBPF program can write into it using bpf\_probe\_write\_user, as we did in other rootkit modules.
|
||||
As we indicated in Table \ref{table:getdents_args}, the \textit{dirent} argument in sys\_getdents64 is a pointer to a user space buffer, and therefore an eBPF program can write into it using bpf\_probe\_write\_user, as we did in other rootkit modules.
|
||||
|
||||
Since we are interested on hiding particular files and directories from the user space, we can take advantage of our writing capabilities at the user buffer to overwrite the d\_reclen attribute of specific linux\_dirent64 entries. By doing this, we can trick a user program into believing that an entry is larger than it is, thus skipping some other entry. This technique has been widely discussed for rootkits by many authors \cite{xcellerator_getdents}, whilst it was firstly introduced for eBPF rootkits by Johann Rehberger \cite{embracethered_getdents}.
|
||||
|
||||
Similarly to what happened in the privilege escalation module in section \ref{section:privesc}, we aim to overwrite the buffer, but we must first wait for it to be filled during the system call, so we must use an \textit{exit} eBPF tracepoint. However, since from this tracepoint we only have access to the return value of the syscall, we must previously save the address of the buffer into an eBPF map from an \textit{enter} tracepoint, so that it can be retrieved form the \textit{exit} tracepoint.
|
||||
Similarly to what happened in the privilege escalation module in Section \ref{section:privesc}, we aim to overwrite the buffer, but we must first wait for it to be filled during the system call, so we must use an \textit{exit} eBPF tracepoint. However, since from this tracepoint we only have access to the return value of the syscall, we must previously save the address of the buffer into an eBPF map from an \textit{enter} tracepoint, so that it can be retrieved form the \textit{exit} tracepoint.
|
||||
|
||||
As we mentioned, we will overwrite the value of d\_reclen of the previous entry to that we want to hide, so that the new d\_reclen equals to the original plus the d\_reclen of the hidden entry. Figure \ref{fig:getdents_technique} shows this technique.
|
||||
|
||||
@@ -1428,12 +1431,12 @@ As we mentioned, we will overwrite the value of d\_reclen of the previous entry
|
||||
|
||||
As we can observe in the figure, by modifying the value of d\_reclen, the user program will skip the entry of file "hideme", and therefore any process listing the available entries of the directory will not show this file.
|
||||
|
||||
Apart from detecting entries by their name, we can also know whether an entry is a file, a directory or of some other type. For this, our rootkit uses the attribute d\_type of the linux\_dirent64 (see table \ref{table:linux_dirent64}), whose value determines the type of file. The most relevant values of the d\_type attribute are shown in table \ref{table:dtype_values} \cite{dtype_dirent}.
|
||||
Apart from detecting entries by their name, we can also know whether an entry is a file, a directory or of some other type. For this, our rootkit uses the attribute d\_type of the linux\_dirent64 (see table \ref{table:linux_dirent64}), whose value determines the type of file. The most relevant values of the d\_type attribute are shown in Table \ref{table:dtype_values} \cite{dtype_dirent}.
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|c|c|}
|
||||
\hline
|
||||
\textbf{Value} & \textbf{Description} \\
|
||||
\textbf{VALUE} & \textbf{DESCRIPTION} \\
|
||||
\hline
|
||||
\hline
|
||||
DT\_DIR (4) & Directory\\
|
||||
@@ -1452,12 +1455,12 @@ Therefore, our rootkit will hide the following entries when found in a linux\_di
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|c|c|c|}
|
||||
\hline
|
||||
\textbf{d\_name} & \textbf{d\_type} & \textbf{Purpose} \\
|
||||
\textbf{d\_name} & \textbf{d\_type} & \textbf{PURPOSE} \\
|
||||
\hline
|
||||
\hline
|
||||
ebpfbackdoor & DT\_DIR (8) & Hide persistence files.\\
|
||||
\hline
|
||||
SECRETDIR & DT\_REG (4) & Provide the rootkit a secret directory where to hide its files.\\
|
||||
SECRETDIR & DT\_REG (4) & Secret directory where the rootkit hides its files.\\
|
||||
\hline
|
||||
\end{tabular}
|
||||
\caption{Directory entries actively hidden by the rootkit.}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
\chapter{Evaluation} \label{chapter:evaluation}
|
||||
This chapter evaluates the malicious capabilities developed in our rootkit by comparing them to the original objectives we presented at the beginning of our research in section \ref{section:project_objectives}. For this, we will analyse whether our rootkit meets the expected functionality by simulating a machine infection in a virtualized environment. A rootkit functionality will be considered fulfilled in the case it can be reproduced successfully in the experimental environment.
|
||||
This chapter evaluates the malicious capabilities developed in our rootkit by comparing them to the original objectives we presented at the beginning of our research in Section \ref{section:project_objectives}. For this, we will analyse whether our rootkit meets the expected functionality by simulating a machine infection in a virtualized environment. A rootkit functionality will be considered fulfilled in the case it can be reproduced successfully in the experimental environment.
|
||||
|
||||
As we mentioned, the following are the functionalities we seeked to implement in our rootkit:
|
||||
\begin{itemize}
|
||||
@@ -13,7 +13,7 @@ As we mentioned, the following are the functionalities we seeked to implement in
|
||||
\section{Experimental setting}
|
||||
The test environment that will be used to showcase the rootkit functionalities consists on two virtual machines running under Oracle VM VirtualBox \cite{virtualbox_page}. One of them will be the host infected with the rootkit, while the other will be used as the attacker machine from which to operate the rootkit client.
|
||||
|
||||
Both virtual machines will be connected via a bridged adapter, as figure \ref{fig:vm_setting_bridged} shows. With this virtual networking setting, the virtual machines connect to a device driver of the host system which injects the data received from the physical network \cite{bridged_networking}. From the virtual machine point of view, both the attacker and the infected machine appear to be physically connected (via a network cable) to the same network interface, each with a different assigned IP address. The name of this interface will be "enp0s3".
|
||||
Both virtual machines will be connected via a bridged adapter, as Figure \ref{fig:vm_setting_bridged} shows. With this virtual networking setting, the virtual machines connect to a device driver of the host system which injects the data received from the physical network \cite{bridged_networking}. From the virtual machine point of view, both the attacker and the infected machine appear to be physically connected (via a network cable) to the same network interface, each with a different assigned IP address. The name of this interface will be "enp0s3".
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -22,7 +22,7 @@ Both virtual machines will be connected via a bridged adapter, as figure \ref{fi
|
||||
\label{fig:vm_setting_bridged}
|
||||
\end{figure}
|
||||
|
||||
Table \ref{table:vm_config_test_environment} shows the role and charactersitics of the two machines. The overall test environment configuration with the described settings is illustrated in figure \ref{fig:test_env}.
|
||||
Table \ref{table:vm_config_test_environment} shows the role and charactersitics of the two machines. The overall test environment configuration with the described settings is illustrated in Figure \ref{fig:test_env}.
|
||||
|
||||
\begin{table}[H]
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{3cm}|>{\centering\arraybackslash}p{3cm}|}
|
||||
@@ -76,10 +76,10 @@ IP address & 192.168.1.127\\
|
||||
|
||||
|
||||
\section{Rootkit compilation and installation} \label{section:compile_install}
|
||||
This section details the steps for a successful compilation and installation fo the rootkit in the target machine. Note that there also exist two scripts \textit{packager.sh} and \textit{deployer.sh} which automatize this process, but these are best indicated for an attacker which wants to quickly assemble the rootkit system, as we will explain in section \ref{section:attack_scenario}.
|
||||
This section details the steps for a successful compilation and installation fo the rootkit in the target machine. Note that there also exist two scripts \textit{packager.sh} and \textit{deployer.sh} which automatize this process, but these are best indicated for an attacker which wants to quickly assemble the rootkit system, as we will explain in Section \ref{section:attack_scenario}.
|
||||
|
||||
\subsection{Compilation}
|
||||
The rootkit source code incorporates two Makefile files that automatize the compilation process with the command \textit{make}. Table \ref{table:makefiles} details the location of the multiple Makefiles that must be executed to compile the different modules of the rootkit (note that in section \ref{section:rootkit_arch} we described the rootkit files and their purpose in detail).
|
||||
The rootkit source code incorporates two Makefile files that automatize the compilation process with the command \textit{make}. Table \ref{table:makefiles} details the location of the multiple Makefiles that must be executed to compile the different modules of the rootkit (note that in Section \ref{section:rootkit_arch} we described the rootkit files and their purpose in detail).
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{2.2cm}|>{\centering\arraybackslash}p{2.2cm}|>{\centering\arraybackslash}p{4cm}|>{\centering\arraybackslash}p{4.5cm}|}
|
||||
@@ -106,7 +106,7 @@ As we can observe in the table, there are two Makefiles:
|
||||
\item A Makefile under src to compile all rootkit files.
|
||||
\end{itemize}
|
||||
|
||||
Therefore, the complete compilation process would consist on the commands shown in snippet \ref{code:compilation}.
|
||||
Therefore, the complete compilation process would consist on the commands shown in Code snippet \ref{code:compilation}.
|
||||
\begin{lstlisting}[language=C, caption={Rootkit and rootkit client compilation.}, label={code:compilation}]
|
||||
//Rootkit files
|
||||
cd src
|
||||
@@ -121,7 +121,7 @@ The output programs corresponding to the rootkit will be stored under a director
|
||||
It must also be noted that, although the rootkit backdoor and C2 capabilities work out of the box, the rest of the rootkit modules need further configuration. This configuration is set via the src/common/constants.h file, and during the rest of this evaluation we will detail the relevant settings for each individual module.
|
||||
|
||||
\subsection{Installation}
|
||||
Once the rootkit programs are compiled, the \textit{tc.o} and \textit{kit} programs must be loaded orderly. Snippet \ref{code:installation} shows the commands to execute for installing the rootkit.
|
||||
Once the rootkit programs are compiled, the \textit{tc.o} and \textit{kit} programs must be loaded orderly. Code snippet \ref{code:installation} shows the commands to execute for installing the rootkit.
|
||||
|
||||
\begin{lstlisting}[language=C, caption={Rootkit installation steps.}, label={code:installation}]
|
||||
//TC egress program
|
||||
@@ -133,7 +133,7 @@ sudo ./bin/kit -t enp0s3
|
||||
|
||||
Note that the network interface enp0s3 may be substituted with any other interface on which the attacker desires the backdoor to be operating.
|
||||
|
||||
Finally, we should create the files that guarantee the rootkit persistence, as shown in snippet \ref{code:persist}.
|
||||
Finally, we should create the files that guarantee the rootkit persistence, as shown in Code snippet \ref{code:persist}.
|
||||
\begin{lstlisting}[language=C, caption={Creation of rootkit persistence files.}, label={code:persist}]
|
||||
echo "* * * * * osboxes /bin/sudo /home/osboxes/TFG/src/helpers/deployer.sh" > /etc/cron.d/ebpfbackdoor
|
||||
echo "osboxes ALL=(ALL:ALL) NOPASSWD:ALL #" > /etc/sudoers.d/ebpfbackdoor
|
||||
@@ -141,15 +141,15 @@ echo "osboxes ALL=(ALL:ALL) NOPASSWD:ALL #" > /etc/sudoers.d/ebpfbackdoor
|
||||
|
||||
The name of the user "osboxes" should be changed by that of the user of the machine to infect, together with the path on which the \textit{deployer.sh} script will be hidden.
|
||||
|
||||
%Maybe this is not the place for story telling? I thought that telling it like this fits nicely here to put some context, but I can try to change it.
|
||||
|
||||
\section{Attack scenario} \label{section:attack_scenario}
|
||||
Although the steps presented in section \ref{section:compile_install} were followed during the rootkit development, an attacker which has compromised a machine and wants to install the rootkit may benefit from a more automated process that quickly prepares all files and installs them in the target machine.
|
||||
Although the steps presented in Section \ref{section:compile_install} were followed during the rootkit development, an attacker which has compromised a machine and wants to install the rootkit may benefit from a more automated process that quickly prepares all files and installs them in the target machine.
|
||||
|
||||
This section presents an hypothetical attack scenario, covering each of the steps the attacker must follow in order to prepare the rootkit and infect a machine:
|
||||
|
||||
A security researcher called 'RED' has managed to exploit a high-severity RCE vulnerability in a critical system controlled by an adversary which was found exposed to the Internet (e.g.: not behind a NAT \cite{nat_comptia}). After this exploitation, RED has now spawned a reverse shell connection with the privileged user 'osboxes', but he knows that the system is often rebooted and that he may lose access soon. Furthermore, the vulnerability he exploited is already well-known and may get patched in the near future, so he needs to persist his access. RED decides to load a classic rootkit consisting of a malicious kernel module, but he finds out that this capability is restricted in the system (e.g.: kernel.modules\_disabled=1 \cite{kernel_modules_restrict}), so he must find an alternative approach. At some point, RED realises that even if kernel modules were restricted, the system administrator did not block eBPF, so he decides to use TripleCross.
|
||||
A security researcher called 'RED' has managed to exploit a high-severity RCE vulnerability in a critical system controlled by an adversary which was found exposed to the Internet (e.g.: not behind a NAT \cite{nat_comptia}). After this exploitation, RED has now spawned a reverse shell connection with the privileged user 'osboxes', but he knows that the system is often rebooted and that he may lose access soon. Furthermore, the vulnerability he exploited is already well-known and may get patched in the near future, so he needs to persist his access. RED decides to load a classic rootkit consisting of a malicious kernel module, but he finds out that this capability is restricted in the system (e.g.: kernel.modules\_disabled=1 \cite{kernel_modules_restrict}), so he must find an alternative approach. Also, it is very possible that the system has an EDR logging events such as loading a kernel module (which almost assuredly will be considered by the EDR given that it is a very relevant event), so he needed to find a more stealthy path anyway. At some point, RED realises that even if kernel modules could not be used, the system administrator did not block eBPF, so he decides to use TripleCross.
|
||||
|
||||
Firstly, RED creates a secret directory where to hide the rootkit, and downloads it, as shown in figure \ref{fig:post_exp}.
|
||||
Firstly, RED creates a secret directory where to hide the rootkit, and downloads it, as shown in Figure \ref{fig:post_exp}.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -160,7 +160,7 @@ Firstly, RED creates a secret directory where to hide the rootkit, and downloads
|
||||
|
||||
Once it is downloaded, RED executes the \textit{packager.sh} script, that will compile the rootkit. Alternatively, an attacker could have compiled it locally and sent it to the remote machine afterwards.
|
||||
|
||||
After the script execution finishes, a folder \textit{apps} has been generated with all the rootkit files. This directory contains all the files and scripts needed for the rootkit installation. RED now executes the \textit{deployer.sh} script, which installs the rootkit and writes the persistence files, as shown in figure \ref{fig:deploy_root}
|
||||
After the script execution finishes, a folder \textit{apps} has been generated with all the rootkit files. This directory contains all the files and scripts needed for the rootkit installation. RED now executes the \textit{deployer.sh} script, which installs the rootkit and writes the persistence files, as shown in Figure \ref{fig:deploy_root}
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -172,10 +172,10 @@ After the script execution finishes, a folder \textit{apps} has been generated w
|
||||
Once the script has been executed, all rootkit modules are loaded and the backdoor is already waiting for commands. RED can now close the reverse shell and open the rootkit client. He now has persistent privileged access to the infected machine.
|
||||
|
||||
\section{Hijacking execution of running processes}
|
||||
Following the infection process described in section \ref{section:attack_scenario}, The rootkit can hijack the execution of running processes by means of the library injection module. This module incorporates two sample programs (\textit{src/helpers/simple\_timer.c and src/helpers/simple\_open.c}), both containing the execution of one of the hijacked syscalls (sys\_timerfd\_settime and sys\_openat respectively). Additionally, the functionality can be tested in any process of the infected machine by changing its settings. Table \ref{table:lib_injection_config} shows how to customize the functionality of the library injection module.
|
||||
Following the infection process described in Section \ref{section:attack_scenario}, The rootkit can hijack the execution of running processes by means of the library injection module. This module incorporates two sample programs (\textit{src/helpers/simple\_timer.c and src/helpers/simple\_open.c}), both containing the execution of one of the hijacked syscalls (sys\_timerfd\_settime and sys\_openat respectively). Additionally, the functionality can be tested in any process of the infected machine by changing its settings. Table \ref{table:lib_injection_config} shows how to customize the functionality of the library injection module.
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{3cm}|>{\centering\arraybackslash}p{5.5cm}|>{\centering\arraybackslash}p{5.5cm}|}
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{2.8cm}|>{\centering\arraybackslash}p{5.2cm}|>{\centering\arraybackslash}p{5.2cm}|}
|
||||
\hline
|
||||
\textbf{FILENAME} & \textbf{CONSTANT} & \textbf{DESCRIPTION}\\
|
||||
\hline
|
||||
@@ -198,7 +198,7 @@ After a successful injection the malicious library will run a reverse shell agai
|
||||
Table \ref{table:lib_injection_config_simple_timer} shows the module configuration for running this attack.
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{3cm}|>{\centering\arraybackslash}p{5.5cm}|>{\centering\arraybackslash}p{5.5cm}|}
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{3cm}|>{\centering\arraybackslash}p{5.5cm}|>{\centering\arraybackslash}p{4cm}|}
|
||||
\hline
|
||||
\textbf{FILENAME} & \textbf{CONSTANT} & \textbf{VALUE}\\
|
||||
\hline
|
||||
@@ -222,7 +222,7 @@ Figure \ref{fig:sc_lib_inj_simple_timer_off} shows the execution of the simple\_
|
||||
\end{figure}
|
||||
|
||||
Once the rootkit is installed it starts the module automatically, looking for system calls from the simple\_timer process.
|
||||
The attacker must in the mean time start a listener (e.g.: with netcat), as shown in figure \ref{fig:sc_lib_inj_nc}.
|
||||
The attacker must in the mean time start a listener (e.g.: with netcat), as shown in Figure \ref{fig:sc_lib_inj_nc}.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -231,7 +231,7 @@ The attacker must in the mean time start a listener (e.g.: with netcat), as show
|
||||
\label{fig:sc_lib_inj_nc}
|
||||
\end{figure}
|
||||
|
||||
Then, the simple\_timer program gets executed on the infected machine. As we can observe in figure \ref{fig:sc_lib_inj_simple_timer_exec}, the injection suceeds and a message is printed from the library.
|
||||
Then, the simple\_timer program gets executed on the infected machine. As we can observe in Figure \ref{fig:sc_lib_inj_simple_timer_exec}, the injection suceeds and a message is printed from the library.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -252,10 +252,10 @@ Figure \ref{fig:sc_lib_inj_nc_success} shows the attacker connected to the rever
|
||||
|
||||
|
||||
\subsection{Test program simple\_open}
|
||||
The library injection module can also be tested with the simple\_timer program, which opens multiple files with sys\_openat. The rootkit configuration for this is shown in table \ref{table:lib_injection_config_simple_open}.
|
||||
The library injection module can also be tested with the simple\_timer program, which opens multiple files with sys\_openat. The rootkit configuration for this is shown in Table \ref{table:lib_injection_config_simple_open}.
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{3cm}|>{\centering\arraybackslash}p{5.5cm}|>{\centering\arraybackslash}p{5.5cm}|}
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{3cm}|>{\centering\arraybackslash}p{5.2cm}|>{\centering\arraybackslash}p{4cm}|}
|
||||
\hline
|
||||
\textbf{FILENAME} & \textbf{CONSTANT} & \textbf{VALUE}\\
|
||||
\hline
|
||||
@@ -269,7 +269,7 @@ src/helpers/ injection\_lib.c & ATTACKER\_IP \& ATTACKER\_PORT & 192.168.1.127 \
|
||||
\label{table:lib_injection_config_simple_open}
|
||||
\end{table}
|
||||
|
||||
As we can observe in figure \ref{fig:sc_lib_inj_simple_open}, when the injection suceeds, a message is printed on screen. Also, the attacker receives a shell, like we showed in figure \ref{fig:sc_lib_inj_nc_success}.
|
||||
As we can observe in figure \ref{fig:sc_lib_inj_simple_open}, when the injection suceeds, a message is printed on screen. Also, the attacker receives a shell, like we showed in Figure \ref{fig:sc_lib_inj_nc_success}.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -283,7 +283,7 @@ As we can observe in figure \ref{fig:sc_lib_inj_simple_open}, when the injection
|
||||
Apart from the test programs, the library injection module can also inject the malicious library on any process of the system that makes use of either sys\_openat or sys\_timerfd\_settime. By hijacking privileged system programs such as systemd, the malicious library can achieve automatic root permissions once it is run (although these are anyways automatically granted via the privilege escalation module). Table \ref{table:lib_injection_config_systemd} shows the module configuration for running an attack against this process.
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{3cm}|>{\centering\arraybackslash}p{5.5cm}|>{\centering\arraybackslash}p{5.5cm}|}
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{3cm}|>{\centering\arraybackslash}p{5.5cm}|>{\centering\arraybackslash}p{4cm}|}
|
||||
\hline
|
||||
\textbf{FILENAME} & \textbf{CONSTANT} & \textbf{VALUE}\\
|
||||
\hline
|
||||
@@ -299,7 +299,7 @@ src/helpers/ injection\_lib.c & ATTACKER\_IP \& ATTACKER\_PORT & 192.168.1.127 \
|
||||
\label{table:lib_injection_config_systemd}
|
||||
\end{table}
|
||||
|
||||
With these configurations, we can run the rootkit and wait for systemd to call one of these syscalls. Eventually this call occurs, and using the debug messages of the rootkit we can get information on what happened, as shown in figure \ref{fig:sch_sc_lib_inj_systemd_debug}.
|
||||
With these configurations, we can run the rootkit and wait for systemd to call one of these syscalls. Eventually this call occurs, and using the debug messages of the rootkit we can get information on what happened, as shown in Figure \ref{fig:sc_lib_inj_systemd_debug}.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -308,7 +308,7 @@ With these configurations, we can run the rootkit and wait for systemd to call o
|
||||
\label{fig:sc_lib_inj_systemd_debug}
|
||||
\end{figure}
|
||||
|
||||
As we can observe in the figure, the rootkit finds the relevant addresses via the technique we described on section \ref{section:lib_injection} and proceeds to overwrite the GOT address. The library is loaded and executed, and since systemd is executed by the root user, the attacker receives a root shell as shown in figure \ref{fig:lib_inj_success_root}. Most importantly, the systemd process does not crash after this attack.
|
||||
As we can observe in the figure, the rootkit finds the relevant addresses via the technique we described on Section \ref{section:lib_injection} and proceeds to overwrite the GOT address. The library is loaded and executed, and since systemd is executed by the root user, the attacker receives a root shell as shown in Figure \ref{fig:lib_inj_success_root}. Most importantly, the systemd process does not crash after this attack.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -319,10 +319,10 @@ As we can observe in the figure, the rootkit finds the relevant addresses via th
|
||||
|
||||
|
||||
\section{Backdoor and C2}
|
||||
The backdoor module works out of the box without any additional configurations needed. It includes the C2 capabilities and the rootkit client used to communicate with the backdoor. As we described in section \ref{subsection:rootkit_manual}, the client allows for the operations listed on table \ref{table:rootkit_client_actions}.
|
||||
The backdoor module works out of the box without any additional configurations needed. It includes the C2 capabilities and the rootkit client used to communicate with the backdoor. As we described in Section \ref{subsection:rootkit_manual}, the client allows for the operations listed on Table \ref{table:rootkit_client_actions}.
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{5cm}|>{\centering\arraybackslash}p{9.5cm}|}
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{5cm}|>{\centering\arraybackslash}p{8.5cm}|}
|
||||
\hline
|
||||
\textbf{PROGRAM ARGUMENTS} & \textbf{ACTION DESCRIPTION}\\
|
||||
\hline
|
||||
@@ -360,7 +360,7 @@ When using a pattern-based trigger, the attacker must indicate the following inf
|
||||
\item The network interface to use for sending the trigger.
|
||||
\end{itemize}
|
||||
|
||||
As figure \ref{fig:sc_eps_rc} shows, the backdoor executes the requested action and starts an encrypted pseudo-shell connection with privileged permissions in which the attacker can introduce commands to be executed. Whenever the connection shall be closed, the attacker introduces the "EXIT" global command (as we explained in section \ref{subsection:rootkit_manual}), which ends the transmission gracefully.
|
||||
As Figure \ref{fig:sc_eps_rc} shows, the backdoor executes the requested action and starts an encrypted pseudo-shell connection with privileged permissions in which the attacker can introduce commands to be executed. Whenever the connection shall be closed, the attacker introduces the "EXIT" global command (as we explained in Section \ref{subsection:rootkit_manual}), which ends the transmission gracefully.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -396,7 +396,7 @@ Figure \ref{fig:sc_eps_srcport} shows the same process but using the TCP source
|
||||
\end{figure}
|
||||
|
||||
\subsection{Spawning phantom shells}
|
||||
A phantom shell can be spawned using the rootkit client by sending pattern-based backdoor triggers. As we explained in section \ref{subsection:c2}, the response to a client command will only be received once a TCP packet is sent from the infected machine to some location. Therefore, we need to wait until any application sends a TCP packet.
|
||||
A phantom shell can be spawned using the rootkit client by sending pattern-based backdoor triggers. As we explained in Section \ref{subsection:c2}, the response to a client command will only be received once a TCP packet is sent from the infected machine to some location. Therefore, we need to wait until any application sends a TCP packet.
|
||||
|
||||
For requesting a phantom shell, the attacker must introduce the following arguments:
|
||||
\begin{itemize}
|
||||
@@ -404,7 +404,7 @@ For requesting a phantom shell, the attacker must introduce the following argume
|
||||
\item The network interface to use for sending the trigger.
|
||||
\end{itemize}
|
||||
|
||||
Once the request is sent by the rootkit client, it will scan the network for the response. As figure \ref{fig:sc_phantom_1} shows, this rootkit client displays an alert whenever a packet is received.
|
||||
Once the request is sent by the rootkit client, it will scan the network for the response. As Figure \ref{fig:sc_phantom_1} shows, this rootkit client displays an alert whenever a packet is received.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -427,7 +427,7 @@ At some point, the infected machine will send a TCP packet to any host. We can s
|
||||
\subsection{eBPF programs control}
|
||||
The rootkit client incorporates two commands to operate the state of the rootkit eBPF programs using the backdoor, enabling to activate or deactivate them as a group.
|
||||
|
||||
Figure \ref{fig:sc_unload_rc} shows how the attacker can detach all eBPF programs (except the backdoor, which as we mentioned in section \ref{subsection:c2} must stay attached to receive further commands).
|
||||
Figure \ref{fig:sc_unload_rc} shows how the attacker can detach all eBPF programs (except the backdoor, which as we mentioned in Section \ref{subsection:c2} must stay attached to receive further commands).
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -436,7 +436,7 @@ Figure \ref{fig:sc_unload_rc} shows how the attacker can detach all eBPF program
|
||||
\label{fig:sc_unload_rc}
|
||||
\end{figure}
|
||||
|
||||
Once the command is executed, we can check that, for instance, the privilege execution module is unloaded, as shown in figure \ref{fig:sc_unload_res}.
|
||||
Once the command is executed, we can check that, for instance, the privilege execution module is unloaded, as shown in Figure \ref{fig:sc_unload_res}.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -445,7 +445,7 @@ Once the command is executed, we can check that, for instance, the privilege exe
|
||||
\label{fig:sc_unload_res}
|
||||
\end{figure}
|
||||
|
||||
Since the backdoor will be still running, the attacker can now request to attach all eBPF programs again, as shown in figure \ref{fig:sc_attach_rc}
|
||||
Since the backdoor will be still running, the attacker can now request to attach all eBPF programs again, as shown in Figure \ref{fig:sc_attach_rc}
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -454,7 +454,7 @@ Since the backdoor will be still running, the attacker can now request to attach
|
||||
\label{fig:sc_attach_rc}
|
||||
\end{figure}
|
||||
|
||||
After the command is executed, all rootkit modules will be loaded again. We can check it by observing the permissions of the user osboxes, as shown in figure \ref{fig:sc_attach}.
|
||||
After the command is executed, all rootkit modules will be loaded again. We can check it by observing the permissions of the user osboxes, as shown in Figure \ref{fig:sc_attach}.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -464,7 +464,7 @@ After the command is executed, all rootkit modules will be loaded again. We can
|
||||
\end{figure}
|
||||
|
||||
|
||||
\subsection{Modifying incoming traffic (PoC)}
|
||||
\subsection{Modifying incoming traffic (PoC)} \label{subsection:poc_evaluation}
|
||||
The backdoor incorporates a simple proof of concept to show how the rootkit may modify incoming network traffic. Although this feature has not been integrated in any of the C2 modules, we considered this functionality to be relevant enough to implement it individually.
|
||||
|
||||
This PoC shows the rootkit client sending a packet with a payload \textit{"XDP\_PoC\_0"} sent to the infected machine port 9000. Upon inspection of this packet, the machine will read the content as \textit{"The previous message has been hidden"}. Figure \ref{fig:sc_poc_rc} shows how the rootkit client can send this packet.
|
||||
@@ -476,7 +476,7 @@ This PoC shows the rootkit client sending a packet with a payload \textit{"XDP\_
|
||||
\label{fig:sc_poc_rc}
|
||||
\end{figure}
|
||||
|
||||
To perform this PoC we will use tcpdump (which we explained in section \ref{subsection:tcpdump}) to inspect the received packets. Figure \ref{fig:sc_tcpdump_before} shows the packet and payload received when the rootkit is not installed.
|
||||
To perform this PoC we will use tcpdump (which we explained in Section \ref{subsection:tcpdump}) to inspect the received packets. Figure \ref{fig:sc_tcpdump_before} shows the packet and payload received when the rootkit is not installed.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -485,7 +485,7 @@ To perform this PoC we will use tcpdump (which we explained in section \ref{subs
|
||||
\label{fig:sc_tcpdump_before}
|
||||
\end{figure}
|
||||
|
||||
Once the rootkit is installed, it will modify the length and contents, as shown in figure \ref{fig:sc_tcpdump_after}.
|
||||
Once the rootkit is installed, it will modify the length and contents, as shown in Figure \ref{fig:sc_tcpdump_after}.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -499,7 +499,7 @@ Once the rootkit is installed, it will modify the length and contents, as shown
|
||||
This functionality has been incorporated in multiple rootkit modules, but it is particularly relevant in the execution hijacking and privilege escalation modules.
|
||||
|
||||
\subsection{Hijacking programs execution}
|
||||
Once the rootkit is installed, it will attempt to hijack any new program that is executed. As we explained in section \ref{section:execution_hijack}, once the rootkit suceeds a malicious program will be run, which will listen for commands from the rootkit client, enabling the attacker to open a plaintext pseudo-shell.
|
||||
Once the rootkit is installed, it will attempt to hijack any new program that is executed. As we explained in Section \ref{section:execution_hijack}, once the rootkit suceeds a malicious program will be run, which will listen for commands from the rootkit client, enabling the attacker to open a plaintext pseudo-shell.
|
||||
|
||||
In this evaluation, we will attempt to test the hijacking process with a test program \textit{src/helpers/simple\_execve} and another with any process of the machine. Table \ref{table:execution_hijack_config} shows some of the configuration options that must be selected before running this module.
|
||||
|
||||
@@ -524,7 +524,7 @@ src/common/ constants.h & TASK\_COMM\_NAME\_RESTRICT\_HIJACK & Name of the progr
|
||||
|
||||
|
||||
\textbf{Test program simple\_execve}\\
|
||||
This program contains a simple sys\_execve call that runs the bash command "pwd", which displays the current directory. As we can observe in table \ref{table:execution_hijack_config_simple_execve}, for this test we will set the PATH\_EXECUTION\_HIJACK\_PROGRAM setting to the path where we have hidden the malicious program, and set the TASK\_COMM\_NAME\_RESTRICT\_HIJACK setting to indicate that we want to hijack calls executed from the simple\_execve program.
|
||||
This program contains a simple sys\_execve call that runs the bash command "pwd", which displays the current directory. As we can observe in Table \ref{table:execution_hijack_config_simple_execve}, for this test we will set the PATH\_EXECUTION\_HIJACK\_PROGRAM setting to the path where we have hidden the malicious program, and set the TASK\_COMM\_NAME\_RESTRICT\_HIJACK setting to indicate that we want to hijack calls executed from the simple\_execve program.
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{3cm}|>{\centering\arraybackslash}p{4.5cm}|>{\centering\arraybackslash}p{4cm}|}
|
||||
@@ -554,7 +554,7 @@ Figure \ref{fig:sc_execve_hijack_before_simple_execve} shows the normal executio
|
||||
\label{fig:sc_execve_hijack_before_simple_execve}
|
||||
\end{figure}
|
||||
|
||||
Once the rootkit is installed, we will open a shell in the infected machine and execute again the simple\_execve program. The result is shown in figure \ref{fig:sc_execve_hijack_simple_execve}.
|
||||
Once the rootkit is installed, we will open a shell in the infected machine and execute again the simple\_execve program. The result is shown in Figure \ref{fig:sc_execve_hijack_simple_execve}.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -563,7 +563,7 @@ Once the rootkit is installed, we will open a shell in the infected machine and
|
||||
\label{fig:sc_execve_hijack_simple_execve}
|
||||
\end{figure}
|
||||
|
||||
As we can observe in the figure, the rootkit hijacked the call and executed the malicious program instead. Each time the malicious program is executed, it alerts us with a message (this would be hidden in a non-experimental case). We can see that it is executed twice (since it needs to run itself as sudo, as we explained in section \ref{subsection:hijack_program_exec}) and then it forks() itself and executes the original program (we can see the output of pwd) and then starts to listen for the rootkit client connections. Figure \ref{fig:sc_execution_hijack_simple_execve_rc} shows how the rootkit client spawns a plaintext pseudo-shell with the malicious program and runs a command.
|
||||
As we can observe in the figure, the rootkit hijacked the call and executed the malicious program instead. Each time the malicious program is executed, it alerts us with a message (this would be hidden in a non-experimental case). We can see that it is executed twice (since it needs to run itself as sudo, as we explained in Section \ref{subsection:hijack_program_exec}) and then it forks() itself and executes the original program (we can see the output of pwd) and then starts to listen for the rootkit client connections. Figure \ref{fig:sc_execution_hijack_simple_execve_rc} shows how the rootkit client spawns a plaintext pseudo-shell with the malicious program and runs a command.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -572,7 +572,7 @@ As we can observe in the figure, the rootkit hijacked the call and executed the
|
||||
\label{fig:sc_execution_hijack_simple_execve_rc}
|
||||
\end{figure}
|
||||
|
||||
As we can observe in the figure, the rootkit client will connect to the malicious program, enabling the attacker to send any command. Once it is received by the malicious program, it will execute it and answer back to the rootkit client with the output according to the plaintext pseudo-shell network protocol. As shown in figure \ref{fig:sc_execution_hijack_im}, the malicious program shows information about the actions that have been executed (which would be hidden in a real scenario).
|
||||
As we can observe in the figure, the rootkit client will connect to the malicious program, enabling the attacker to send any command. Once it is received by the malicious program, it will execute it and answer back to the rootkit client with the output according to the plaintext pseudo-shell network protocol. As shown in Figure \ref{fig:sc_execution_hijack_im}, the malicious program shows information about the actions that have been executed (which would be hidden in a real scenario).
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -583,7 +583,7 @@ As we can observe in the figure, the rootkit client will connect to the maliciou
|
||||
|
||||
|
||||
\textbf{Hijacking the execution of any program}\\
|
||||
As we mentioned in section \ref{section:execution_hijack}, it is possible that programs fail to be hijacked due to page faults. Because of this, it can take a long time for an specific program (such as bash) to trigger the execution of the malicious program so that the attacker can connect via the plaintext pseudo-shell. This is the reason why the rootkit can also be set to attempt hijacking any program execution from the system instead of restricting the operation to a single process. In this mode, the rootkit will attempt to hijack any sys\_execve call until it suceeds once, afterwards the execution hijacking module will be deactivated. Table \ref{} shows the configuration for this mode.
|
||||
As we mentioned in Section \ref{section:execution_hijack}, it is possible that programs fail to be hijacked due to page faults. Because of this, it can take a long time for an specific program (such as bash) to trigger the execution of the malicious program so that the attacker can connect via the plaintext pseudo-shell. This is the reason why the rootkit can also be set to attempt hijacking any program execution from the system instead of restricting the operation to a single process. In this mode, the rootkit will attempt to hijack any sys\_execve call until it succeeds once, afterwards the execution hijacking module will be deactivated. Table \ref{table:execution_hijack_config_any} shows the configuration for this mode.
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{3cm}|>{\centering\arraybackslash}p{4.5cm}|>{\centering\arraybackslash}p{4cm}|}
|
||||
@@ -604,11 +604,11 @@ src/common/ constants.h & TASK\_COMM\_NAME\_RESTRICT\_HIJACK & ""\\
|
||||
\label{table:execution_hijack_config_any}
|
||||
\end{table}
|
||||
|
||||
The process will be identical to that shown with the test program simple\_execve. Once a sys\_execve call is hijacked, the malicious program will listen for comamnds sent from the rootkit client, as we showed previously in figure \ref{fig:sc_execution_hijack_simple_execve_rc}.
|
||||
The process will be identical to that shown with the test program simple\_execve. Once a sys\_execve call is hijacked, the malicious program will listen for comamnds sent from the rootkit client, as we showed previously in Figure \ref{fig:sc_execution_hijack_simple_execve_rc}.
|
||||
|
||||
|
||||
\subsection{Privilege escalation}
|
||||
As we showed in section \ref{section:privesc}, the privilege escalation module tampers with system calls buffers to modify the contents read from the \textit{/etc/sudoers} file by the sudo process. Figure \ref{fig:sc_sudo_prev} shows the sudo permissions of user osboxes previously to the installation of the rootkit. As we can observe, it has sudo privileges, but requires a password.
|
||||
As we showed in Section \ref{section:privesc}, the privilege escalation module tampers with system calls buffers to modify the contents read from the \textit{/etc/sudoers} file by the sudo process. Figure \ref{fig:sc_sudo_prev} shows the sudo permissions of user osboxes previously to the installation of the rootkit. As we can observe, it has sudo privileges, but requires a password.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -626,7 +626,7 @@ Once the rootkit is installed, every time the sudo process requests to read the
|
||||
\label{fig:sc_sudo_after}
|
||||
\end{figure}
|
||||
|
||||
Note that this modification only applies to the sudo process. For instance, if any user wants to read the \textit{/etc/sudoers} file, it appears intact as shown in figure \ref{fig:sc_sudoers}.
|
||||
Note that this modification only applies to the sudo process. For instance, if any user wants to read the \textit{/etc/sudoers} file, it appears intact as shown in Figure \ref{fig:sc_sudoers}.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -636,13 +636,13 @@ Note that this modification only applies to the sudo process. For instance, if a
|
||||
\end{figure}
|
||||
|
||||
\subsection{Rootkit stealth}
|
||||
As we presented in section \ref{section:rootkti_stealth}, the following files and directories will be hidden by the rootkit:
|
||||
As we presented in Section \ref{section:rootkti_stealth}, the following files and directories will be hidden by the rootkit:
|
||||
\begin{itemize}
|
||||
\item Files named "ebpfbackdoor", to hide those corresponding to the rootkit persistence.
|
||||
\item Entire directories named "SECRETDIR", to hide the rootkit files.
|
||||
\end{itemize}
|
||||
|
||||
The files and directories being hidden can be modified by using the settings shown in table \ref{table:rootkit_stealth_config}.
|
||||
The files and directories being hidden can be modified by using the settings shown in Table \ref{table:rootkit_stealth_config}.
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{3cm}|>{\centering\arraybackslash}p{4.5cm}|>{\centering\arraybackslash}p{6cm}|}
|
||||
@@ -662,7 +662,7 @@ src/common/ constants.h & SECRET\_FILE\_PERSISTENCE\_NAME & Name of the file to
|
||||
We will now test this module in the infected machine.
|
||||
|
||||
\textbf{Hiding rootkit directory}\\
|
||||
In the attack scenario we described in section \ref{section:attack_scenario}, the SECRETDIR directory was created under \textit{/home/osboxes} and it was set as the root directory where to hide the rootkit files. Table \ref{} details the rootkit configuration needed to hide this directory.
|
||||
In the attack scenario we described in Section \ref{section:attack_scenario}, the SECRETDIR directory was created under \textit{/home/osboxes} and it was set as the root directory where to hide the rootkit files. Table \ref{table:rootkit_stealth_config_dir} details the rootkit configuration needed to hide this directory.
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{3cm}|>{\centering\arraybackslash}p{4.5cm}|>{\centering\arraybackslash}p{6cm}|}
|
||||
@@ -677,7 +677,7 @@ src/common/ constants.h & SECRET\_DIRECTORY\_NAME\_HIDE & "SECRETDIR"\\
|
||||
\label{table:rootkit_stealth_config_dir}
|
||||
\end{table}
|
||||
|
||||
Listing the files and directories under the command \textit{ls} yields the results shown in figure \ref{fig:sc_stealth_dir_before}.
|
||||
Listing the files and directories under the command \textit{ls} yields the results shown in Figure \ref{fig:sc_stealth_dir_before}.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -686,7 +686,7 @@ Listing the files and directories under the command \textit{ls} yields the resul
|
||||
\label{fig:sc_stealth_dir_before}
|
||||
\end{figure}
|
||||
|
||||
After the rootkit is loaded, we can observe in figure \ref{fig:sc_stealth_dir_after} that the directory SECRETDIR is not visible anymore.
|
||||
After the rootkit is loaded, we can observe in Figure \ref{fig:sc_stealth_dir_after} that the directory SECRETDIR is not visible anymore.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -696,7 +696,7 @@ After the rootkit is loaded, we can observe in figure \ref{fig:sc_stealth_dir_af
|
||||
\end{figure}
|
||||
|
||||
\textbf{Hiding persistence files}\\
|
||||
Hiding the \textit{ebpfbackdoor} files can be achieved using the configuration shown in table \ref{}
|
||||
Hiding the \textit{ebpfbackdoor} files can be achieved using the configuration shown in table \ref{table:rootkit_stealth_config_file}.
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{3cm}|>{\centering\arraybackslash}p{4.5cm}|>{\centering\arraybackslash}p{6cm}|}
|
||||
@@ -711,7 +711,7 @@ src/common/ constants.h & SECRET\_FILE\_PERSISTENCE\_NAME & "ebpfbackdoor"\\
|
||||
\label{table:rootkit_stealth_config_file}
|
||||
\end{table}
|
||||
|
||||
As we can observe in figure \ref{fig:sc_stealth_file_before}, this file is visible before installing the backdoor.
|
||||
As we can observe in Figure \ref{fig:sc_stealth_file_before}, this file is visible before installing the backdoor.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -720,7 +720,7 @@ As we can observe in figure \ref{fig:sc_stealth_file_before}, this file is visib
|
||||
\label{fig:sc_stealth_file_before}
|
||||
\end{figure}
|
||||
|
||||
However, once the rootkit is installed, the file will not be listed under the directory (or any other), as shown in figure \ref{fig:sc_stealth_file_after}.
|
||||
However, once the rootkit is installed, the file will not be listed under the directory (or any other), as shown in Figure \ref{fig:sc_stealth_file_after}.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
@@ -731,7 +731,7 @@ However, once the rootkit is installed, the file will not be listed under the di
|
||||
|
||||
|
||||
\section{Rootkit persistence}
|
||||
The files at \textit{/etc/cron.d} and \textit{/etc/sudoers.d} ensure the persistence of the rootkit in the infected system. As we explained in section \ref{section:persistence}, these files are created by the \textit{deployer.sh} script before loading the rootkit. In this script, two constants define the contents of the entry written in these directories, as shown in table \ref{table:rootkit_persistence_config}.
|
||||
The files at \textit{/etc/cron.d} and \textit{/etc/sudoers.d} ensure the persistence of the rootkit in the infected system. As we explained in Section \ref{section:persistence}, these files are created by the \textit{deployer.sh} script before loading the rootkit. In this script, two constants define the contents of the entry written in these directories, as shown in Table \ref{table:rootkit_persistence_config}.
|
||||
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{3cm}|>{\centering\arraybackslash}p{4.5cm}|>{\centering\arraybackslash}p{6cm}|}
|
||||
@@ -771,7 +771,7 @@ src/helpers/ deployer.sh & SUDO\_PERSIST & "osboxes ALL=(ALL:ALL) NOPASSWD:ALL \
|
||||
In the previous sections, we have explained the steps needed for using the different rootkit modules and displayed its functionalities in a test environment. As we saw, we were able to build at least one rootkit-like functionality using each of the capabilities we proposed at the beginning of this research work for our rootkit. As a summary, for each of these capabilities, we achieved the following:
|
||||
\begin{itemize}
|
||||
\item For hijacking running programs, we built a library injection mechanism that does not crash the process and thus allows for stealthy execution of code. We also incorporated a remote control capability for the malicious injected library so that we could execute commands remotely from the rootkit client.
|
||||
\item With respect to backdoor and C2 capabilities we seeked for the rootkit, we built a comprehensive C2 system supporting multiple stealthy backdoor triggers and encrypted communication systems that allow for executing commands using the rootkit client, apart from an advanced method for exfiltrating data by modifying the outgoing traffic. The multiple stealthy features, as we explained in section \ref{subsection:triggers}, allow for hiding data from network monitoring software using multiple techniques. Also, we demonstrated the backdoor capabilities for receiving and transmitting actions that manipulate the state of eBPF programs.
|
||||
\item With respect to backdoor and C2 capabilities we seeked for the rootkit, we built a comprehensive C2 system supporting multiple stealthy backdoor triggers and encrypted communication systems that allow for executing commands using the rootkit client, apart from an advanced method for exfiltrating data by modifying the outgoing traffic. The multiple stealthy features, as we explained in Section \ref{subsection:triggers}, allow for hiding data from network monitoring software using multiple techniques. Also, we demonstrated the backdoor capabilities for receiving and transmitting actions that manipulate the state of eBPF programs.
|
||||
\item In the context of manipulating system calls, this was a key capability used in multiple of the rootkit modules. We were able to hijack the execution of programs or modify the contents of critical files in the system, such as \textit{/etc/sudoers}, which granted any rootkit user program privileged permissions.
|
||||
\item With respect to rootkit persistence, we built a system that allows for surviving reboots, not only ensuring that the rootkit will be installed after one of these events, but also that the root permissions that were once granted to the rootkit the first time it was installed are maintained across reboots.
|
||||
\item The stealth module we incorporated allows for hiding the directory where the rootkit is stored form the user, along with those files responsible from ensuring the rootkit persistence.
|
||||
|
||||
@@ -57,7 +57,7 @@ In its later version, Jynx2 \cite{jynx2_github}, the rootkit incorporated
|
||||
other mechanisms focused on hiding the rootkit activity
|
||||
\cite{jynx2_infosecinstitute}. This included hiding Jynx's connections by
|
||||
hooking read calls at the \textit{/proc} filesystem (which we covered in
|
||||
section \ref{section:proc_filesystem} so that processes related with the
|
||||
Section \ref{section:proc_filesystem} so that processes related with the
|
||||
rootkit activity remain undisclosed. Other functionalities include file
|
||||
hiding, privilege escalation, or multi-factor authentication in the rootkit
|
||||
backdoor.
|
||||
@@ -235,7 +235,7 @@ advancements, the capabilities of eBPF helpers, such as
|
||||
bpf\_probe\_write\_user() or the possibility of hooking and modifying
|
||||
syscalls, were first discussed in the CCC presentation. On the other
|
||||
hand, the work presented at DEFCON 27 introduces the ROP technique for
|
||||
achieving library injection, which we have discussed in section
|
||||
achieving library injection, which we have discussed in Section
|
||||
\ref{subsection:rop_ebpf}. NCC Group has made publicly available a set of
|
||||
programs developed in BCC showing a proof of concept for this technique
|
||||
\cite{evil_ebpf_github}.
|
||||
@@ -263,9 +263,9 @@ The work of Fournier and Afchainte is developed around the three
|
||||
fundamental pillars on which eBPF programs operate: the network, the user
|
||||
space and the kernel space.
|
||||
\begin{itemize}
|
||||
\item In the network, ebpfkit incorporates the first eBPF backdoor with C2 capabilities powered by an XDP and TC program. It presents for the first time the TCP retransmissions technique we explained in section \ref{subsection:tcp} for sending new packets from the backdoor. It also incorporates a network scanning functionality based on this technique.
|
||||
%TODO note that the chapter 3 needs to be modified to explain the technique a bit.
|
||||
\item In the kernel space, ebpfkit incorporates hooks at open and read syscalls, with the purpose of hiding the rootkit (such as hiding the PID at the proc filesystem) or adding custom ssh keys when the keys file is read by the sshd process. Most importantly, it incorporates the first technique to hide the warning log messages shown in the kernel log buffer, which we mentioned in section \ref{subsection:bpf_probe_write_apps}. This technique works by hooking sys\_read calls during the attachment process, during which the eBPF program will indicate the kernel that nothing is available to be read from the buffer by means of bpf\_override\_return(), followed by overwritting the warning messages using bpf\_probe\_write\_user().
|
||||
\item In the network, ebpfkit incorporates the first eBPF backdoor with C2 capabilities powered by an XDP and TC program. It presents for the first time the TCP retransmissions technique we explained in Section \ref{subsection:tcp} for sending new packets from the backdoor. It also incorporates a network scanning functionality based on this technique.
|
||||
|
||||
\item In the kernel space, ebpfkit incorporates hooks at open and read syscalls, with the purpose of hiding the rootkit (such as hiding the PID at the proc filesystem) or adding custom ssh keys when the keys file is read by the sshd process. Most importantly, it incorporates the first technique to hide the warning log messages shown in the kernel log buffer, which we mentioned in Section \ref{subsection:bpf_probe_write_apps}. This technique works by hooking sys\_read calls during the attachment process, during which the eBPF program will indicate the kernel that nothing is available to be read from the buffer by means of bpf\_override\_return(), followed by overwritting the warning messages using bpf\_probe\_write\_user().
|
||||
\item At user space, ebpfkit incorporates multiple techniques to target specific versions of common software by hooking their function calls using uprobes and modifying its arguments. An example of this is bypassing the protection of Runtime Application Self Protection (RASP) software \cite{rasps}, which are programs oriented towards monitoring the data in a program to prevent malicious data input by an attacker, so that a SQL injection attack \cite{sql_injection} could take place.
|
||||
\end{itemize}
|
||||
|
||||
@@ -334,3 +334,38 @@ It must also be noted that, although the ability to modify outgoing traffic and
|
||||
|
||||
In summary, TripleCross offers new techniques and modifies others presented in previous research work, while at the same time takes as a basis both well-known techniques in rootkit development and also those already presented in previous eBPF rootkits which are key for certain functionalities, such as ebpfkit's TCP retransmissions for duplicating packets.
|
||||
|
||||
|
||||
\section{Rootkit features comparison}
|
||||
This chapter compares the overall features and capabilities of the rootkits described in this chapter. Table \ref{table:rootkit_comparison} shows this comparison.
|
||||
|
||||
\newgeometry{hmargin=3cm,vmargin=2cm}
|
||||
\thispagestyle{lscape}
|
||||
\begin{landscape}
|
||||
\begin{table}[htbp]
|
||||
\begin{tabular}{|>{\centering\arraybackslash}p{3cm}|>{\centering\arraybackslash}p{3cm}|>{\centering\arraybackslash}p{3cm}|>{\centering\arraybackslash}p{3cm}|>{\centering\arraybackslash}p{3cm}|>{\centering\arraybackslash}p{3cm}|>{\centering\arraybackslash}p{3cm}|>{\centering\arraybackslash}p{3cm}|}
|
||||
\hline
|
||||
\textbf{ROOTKIT AND TYPE} & \textbf{BACKDOOR \& C2} & \textbf{CODE EXECUTION} & \textbf{DATA MANIPULATION} & \textbf{STEALTH} & \textbf{PRIVILEGE ESCALATION} & \textbf{PERSISTENCE}\\
|
||||
\hline
|
||||
\hline
|
||||
Jynx2 (LD\_PRELOAD) & accept() hijacking & LD\_PRELOAD & User space & Files hiding. Process hiding. & Yes & No\\
|
||||
\hline
|
||||
Azazel (LD\_PRELOAD) & accept() hijacking & LD\_PRELOAD & User space & Files hiding. Process hiding. & Yes & No\\
|
||||
\hline
|
||||
SucKIT (/dev/kmem) & Magic packet trigger & Syscall table hijack with /dev/kmem & User and kernel space & No & No & /sbin/init hijack\\
|
||||
\hline
|
||||
Diamorphine (LKM) & Local, via kill signals & At kernel Kprobes & Kernel space (kprobes) & Files hiding. LKM hiding. & Yes & No\\
|
||||
\hline
|
||||
Reptile (LKM) & Port-knocking & At kernel Kprobes & User space (files) and kernel space (kprobes) & Files hiding. LKM hiding. Process hiding. & Yes & Yes\\
|
||||
\hline
|
||||
Ebpfkit (eBPF) & Port filtering. Data exfiltration. Network scans. & At eBPF programs only & User space (files, uprobes) Kernel space (kprobes) & BPF hiding. Files hiding. & No & Init system\\
|
||||
\hline
|
||||
boopkit (eBPF) & Command execution. Boop vectors. Remote shell. & User program and eBPF programs. & No & BPF process hiding. & No & No\\
|
||||
\hline
|
||||
TripleCross (eBPF) & Command execution. Pattern \& Multi packet trigger. Remote shells. & User and eBPF programs. Library injection and execution hijacking. & User space (files, uprobes) Kernel space (tracepoints). & Files hiding. Packet payload hiding. & Yes & Cron and sudo\\
|
||||
\hline
|
||||
\end{tabular}
|
||||
\caption{Overall rootkit features comparison.}
|
||||
\label{table:rootkit_comparison}
|
||||
\end{table}
|
||||
\end{landscape}
|
||||
\restoregeometry
|
||||
|
||||
@@ -5,7 +5,7 @@ invested on research, development and documentation writing, along with
|
||||
other indirect costs associated to the project activities.
|
||||
|
||||
\section{Gantt chart}
|
||||
Figure \ref{gantt_chart} shows a Gantt presenting the different stages of the project and the distribution of time between them. As we can observe in the figure, the project can be divided into three main sections:
|
||||
Figure \ref{fig:gantt_chart} shows a Gantt presenting the different stages of the project and the distribution of time between them. As we can observe in the figure, the project can be divided into three main sections:
|
||||
\begin{itemize}
|
||||
\item Preliminary research on previous work.
|
||||
\item Development of each rootkit module.
|
||||
@@ -14,13 +14,17 @@ Figure \ref{gantt_chart} shows a Gantt presenting the different stages of the pr
|
||||
|
||||
It is relevant to note that in this research work, because of the complexity and variety of functionalities of the eBPF system, each of the offensive capabilities of eBPF has been discovered and implemented as a rootkit module individually. Therefore, there has not existed a single iteration of analysis, design and implementation, but rather multiple iterations have been made to develop each module. This is the reason why, if we focus our view in the development stages, each consists on at least one analysis and multiple design and implementation activities.
|
||||
|
||||
|
||||
\newgeometry{hmargin=3cm,vmargin=2cm}
|
||||
\thispagestyle{lscape}
|
||||
\begin{landscape}
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
\includegraphics[width=15.4cm]{gantt_chart.jpg}
|
||||
\includegraphics[width=21cm]{gantt_chart.jpg}
|
||||
\caption{Gantt chart of the project.}
|
||||
\label{fig:gantt_chart}
|
||||
\end{figure}
|
||||
\end{landscape}
|
||||
\restoregeometry
|
||||
|
||||
\section{Estimated costs}
|
||||
This section presents an estimation of the costs associated with the personnel conducting the activities described in the Gantt chart in addition to all costs derived from the development of this work.
|
||||
@@ -52,7 +56,7 @@ Project manager & 40,000 € & 19.23 € \\
|
||||
\label{table:salary_personnel}
|
||||
\end{table}
|
||||
|
||||
Given the different responsabilities of the team members on the project, table \ref{table:hours_personnel} shows the number of hours which each person dedicates daily to the project in average when perfoming each of the tasks (that is, the length of a working day when assigned to each task).
|
||||
Given the different responsabilities of the team members on the project, Table \ref{table:hours_personnel} shows the number of hours which each person dedicates daily to the project in average when perfoming each of the tasks (that is, the length of a working day when assigned to each task).
|
||||
|
||||
Also, note that our own RawTCP\_Lib library is a relevant part of this project but it has been developed outside of the scope of this research. Therefore, we will consider it as an estimated 20-days long 4 hours/day development by the programmer.
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ At the beginning of this project, we proposed to study the offensive
|
||||
capabilities of eBPF at the network level and both user- and kernel-space.
|
||||
Our research shows that a malicious eBPF program can drop any network
|
||||
packet and have read and write access over both incoming and outgoing
|
||||
network traffic using XDP and TC programs. We also discusses how it can
|
||||
network traffic using XDP and TC programs. We also discuss how it can
|
||||
read and write any memory at the user-space using kprobes and tracepoints,
|
||||
and that it can tamper with user data passed to the kernel at system calls,
|
||||
although kernel memory cannot be written. In the end, these capabilities
|
||||
|
||||
@@ -16,6 +16,8 @@ hmargin=3cm
|
||||
\renewcommand{\baselinestretch}{1.15}
|
||||
\parskip=6pt
|
||||
|
||||
\usepackage{pdflscape}
|
||||
|
||||
% COLORS for cover and code
|
||||
\usepackage[table]{xcolor}
|
||||
\definecolor{azulUC3M}{RGB}{0,0,102}
|
||||
@@ -43,8 +45,9 @@ hmargin=3cm
|
||||
\usepackage[babel, english=american]{csquotes}
|
||||
\AtBeginEnvironment{quote}{\small}
|
||||
|
||||
% FOOTER
|
||||
\usepackage{fancyhdr}
|
||||
\usepackage{tikz}
|
||||
% FOOTER
|
||||
\pagestyle{fancy}
|
||||
\fancyhf{}
|
||||
\renewcommand{\headrulewidth}{1pt}
|
||||
@@ -53,6 +56,22 @@ hmargin=3cm
|
||||
\fancyhead[RO]{\rightmark}
|
||||
\rfoot{\thepage}
|
||||
\fancypagestyle{plain}{\pagestyle{fancy}}
|
||||
\fancypagestyle{plainnofancy}%
|
||||
{%
|
||||
\fancyhf{}
|
||||
\renewcommand{\headrulewidth}{0pt}
|
||||
\rfoot{\thepage}
|
||||
}
|
||||
|
||||
%LANDSCAPE PAGES
|
||||
\fancypagestyle{lscape}{%
|
||||
\fancyhf{} % clear all header and footer fields
|
||||
\fancyfoot{%
|
||||
\tikz[remember picture,overlay]
|
||||
\node[outer sep=1cm,above,rotate=90] at (current page.45) {\thepage};}
|
||||
\renewcommand{\headrulewidth}{0pt}
|
||||
\renewcommand{\footrulewidth}{0pt}
|
||||
}
|
||||
|
||||
% TITLES
|
||||
\usepackage{titlesec}
|
||||
@@ -229,12 +248,24 @@ hmargin=3cm
|
||||
%----------
|
||||
\renewcommand\abstractname{\large\uppercase{Summary}}
|
||||
\begin{abstract}
|
||||
\thispagestyle{plain}
|
||||
\thispagestyle{plainnofancy}
|
||||
\setcounter{page}{3}
|
||||
|
||||
% TODO SUMMARY
|
||||
|
||||
\textbf{Keywords:}
|
||||
% So I read that acronyms are not allowed in abstracts and I should write the full name. At the same time, the official ebpf page says it is not an acronym anymore...
|
||||
|
||||
eBPF is a technology introduced in the 3.18 version of the Linux kernel that allows running code in the kernel without the need of loading a kernel module. Although originally intended for filtering packets, eBPF programs can be used for network monitoring, accessing kernel-exclusive resources and tracing activities at the user and kernel space. This has positioned eBPF as a leading environment for the development of network, security and observability tools. During the last years, however, eBPF has been found to be at the heart of the latest innovation on the development of rootkits.
|
||||
|
||||
This work identifies the offensive capabilities of eBPF that could be weaponized by a threat actor. Based on them, we have developed an
|
||||
eBPF-based rootkit that uses these capabilities to showcase multiple malicious use cases. Our rootkit, named TripleCross, incorporates (1) a
|
||||
library injection module to execute malicious code by writing at processes virtual memory; (2) an execution hijacking module that modifies data passed to the kernel to execute malicious programs; (3) a local privilege escalation module that allows for running malicious programs with root privileges; (4) a backdoor with C2 capabilities that can monitor the network and execute commands sent from a remote rootkit client, incorporating multiple backdoor triggers so that these actions are transmitted with stealth in mind; (5) a rootkit client program that allows an attacker to establish 3 different types of shell-like connections for sending commands and actions that control the rootkit state
|
||||
remotely; (6) a persistence module that ensures the
|
||||
rootkit remains installed maintaining full privileges even after a reboot event; and (7) a stealth module that hides rootkit-related files and directories from the user.
|
||||
|
||||
TripleCross demonstrates the existing danger when running eBPF programs, a
|
||||
technology also available by default in most distributions. It is intended for being used in pentesting and red teaming exercises.
|
||||
|
||||
%Apparently I must not repeat those appearing in the title
|
||||
\textbf{Keywords: Backdoor; Berkeley Packet Filter; Implant; Command and Control; Linux kernel; Malware; Computer security}
|
||||
% TODO KEYWORDS
|
||||
|
||||
\vfill
|
||||
@@ -242,29 +273,15 @@ hmargin=3cm
|
||||
\newpage
|
||||
\thispagestyle{empty}
|
||||
\mbox{}
|
||||
|
||||
|
||||
|
||||
|
||||
%----------
|
||||
% DEDICATION
|
||||
%----------
|
||||
\chapter*{Dedication}
|
||||
|
||||
\setcounter{page}{5}
|
||||
|
||||
% TODO DEDICATION
|
||||
|
||||
\vfill
|
||||
|
||||
\newpage
|
||||
\thispagestyle{empty}
|
||||
\mbox{}
|
||||
|
||||
\chapter*{Abstract}
|
||||
|
||||
\setcounter{page}{5}
|
||||
|
||||
% TODO ABSTRACT
|
||||
|
||||
\thispagestyle{plainnofancy}
|
||||
\vfill
|
||||
|
||||
\newpage
|
||||
@@ -280,7 +297,8 @@ hmargin=3cm
|
||||
%General indexes
|
||||
%-
|
||||
\tableofcontents
|
||||
\thispagestyle{fancy}
|
||||
|
||||
|
||||
|
||||
\newpage
|
||||
\thispagestyle{empty}
|
||||
@@ -306,16 +324,15 @@ hmargin=3cm
|
||||
\thispagestyle{empty}
|
||||
\mbox{}
|
||||
|
||||
|
||||
%----------
|
||||
% INTRODUCTION
|
||||
%----------
|
||||
|
||||
\clearpage
|
||||
\pagenumbering{arabic}
|
||||
|
||||
% This prevents the underscores going out of the margins
|
||||
\renewcommand\_{\textunderscore\allowbreak}
|
||||
|
||||
\input{chapters/chapter1}
|
||||
\input{chapters/chapter2}
|
||||
\input{chapters/chapter3}
|
||||
@@ -332,8 +349,6 @@ hmargin=3cm
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
%----------
|
||||
% BIBLIOGRAPHY
|
||||
%----------
|
||||
|
||||
Reference in New Issue
Block a user