Continued with execve hijacking.

This commit is contained in:
h3xduck
2022-06-13 22:16:34 -04:00
parent a1a41b02df
commit 163f923c55
11 changed files with 21121 additions and 20348 deletions

View File

@@ -636,6 +636,38 @@ AMD64 Architecture Processor Supplement},
@online{syscall_reference,
title={Linux Syscall Reference (64bit)},
url={https://syscalls64.paolostivanin.com/}
},
@online{code_kernel_execve,
indextitle={Linux kernel code},
url={https://elixir.bootlin.com/linux/v5.11/source/fs/exec.c#L2054}
},
@online{environ,
title={How to Set and List Environment Variables in Linux},
date={2021-06-03},
url={https://linuxize.com/post/how-to-set-and-list-environment-variables-in-linux/}
},
@online{execve_man,
title={execve(2) — Linux manual page},
url={https://man7.org/linux/man-pages/man2/execve.2.html}
},
@online{bpf_probe_write_user_errors,
title={[iovisor-dev] Accessing user memory and minor page faults},
date = {2017-08-06},
url={https://lists.linuxfoundation.org/pipermail/iovisor-dev/2017-September/001035.html}
},
@online{c_standard_main,
title={Main function},
url={https://en.cppreference.com/w/c/language/main_function}
},
@online{busybox_argv,
title={BusyBox Examples},
url={https://en.wikipedia.org/wiki/BusyBox#Examples}
}

View File

@@ -699,7 +699,7 @@ Nowadays, most Linux distributions have set value 1 to this parameter, therefore
\section{Memory management in Linux}
Multiple of the techniques incorporated in our rootkit require a deep understanding into how memory is managed in a Linux process. Therefore, in this section we will present all the background about memory management needed for our later discussion of the offensive capabilities of eBPF in this context.
\subsection{Memory pages and faults}
\subsection{Memory pages and faults} \label{subsection:mem_faults}
Linux systems divide the available random access memory (RAM) into 'pages', subsections of an specific length, usually 4 KB. The collection of all pages is called physical memory.
Likewise, individual memory sections need to be assigned to each running process in the system, but instead of assigning a set of pages from physical memory, a new address space is defined, named virtual memory, which is divided into pages as well. These virtual memory pages are related to physical memory pages via a page table, so that each virtual memory address of a process can be translated into a real, physical memory address in RAM \cite{mem_page_arch}. Figure \ref{fig:mem_arch_pages} shows a diagram of the described architecture.

View File

@@ -237,7 +237,7 @@ 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. 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{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.}
\end{itemize}

View File

@@ -486,12 +486,132 @@ Injecting that string into the read file will grant us with password-less sudo p
Although the previous is sufficient for tricking the sudo process into believing we have sudo privileges, it can happen that an user (in this case, osboxes) already has an entry in the \textit{/etc/sudoers} file. When this happens, the sudo process usually chooses the last entry that appears on the file or fails.
Although not the most elegant solution, the solution for this issue incorporated in our rootkit is that tracepoint will continue writing \# symbols until an error happens (thus indicating we reached the end of the file).
%TODO INCORPORATE FINAL FIGURE OF OVERWRITEN SUDOERS
Although not the most elegant solution, the solution for this issue incorporated in our rootkit is that the tracepoint program will continue writing \# symbols until an error happens (thus indicating we reached the end of the file).
\end{enumerate}
\section{Execution hijacking module}
This section describes how the rootkit can hijack the execution of programs. Although in principle eBPF in the kernel cannot start the execution of a program by itself, this module shows how a malicious rootkit may take advantage of benign programs in order to execute malicious code in the user space. Therefore, we aim to achieve two main goals:
\begin{itemize}
\item Execute a malicious user program taking advantage of other program's execution.
\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}
\begin{table}[htbp]
\begin{tabular}{|c|>{\centering\arraybackslash}p{7cm}|}
\hline
Argument & Description\\
\hline
\hline
const char \_\_user *filename & Path and filename of the file to execute\\
\hline
const char \_\_user *const \_\_user *argv & NULL-terminated array with arguments passed to the program\\
\hline
const char \_\_user *const \_\_user *envp & NULL-terminated array with the environment variables associated to the executed program \cite{environ}\\
\hline
\end{tabular}
\caption{Arguments of system call sys\_execve.}
\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.
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.
\begin{figure}[htbp]
\centering
\includegraphics[width=14cm]{summ_execve_hijack.png}
\caption{Overview of execution hijacking attack.}
\label{fig:summ_execve_hijack}
\end{figure}
As we will discuss, apart from running the original program, the malicious program will run itself as sudo (taking advantage of the privilege escalation module) and then connecting to the rootkit client.
\subsection{Overwriting sys\_execve}
We have mentioned the possibility of overwriting the parameters of the sys\_execve syscall. However, after loading an eBPF \textit{enter} tracepoint attached to sys\_execve and writing into any of this buffers, we found three scenarios:
\begin{itemize}
\item The helper successfully overwrites the user buffers.
\item The helper fails to overwrite all or some of the buffers.
\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.
Because of the fact that programs calling sys\_execve will be completely replaced by the new program, we can find this function used commonly in two contexts:
\begin{itemize}
\item User programs which execute a new program as a child, but they do not want to be terminated themselves. For this, they call a fork() and then execute execve() (which calls the sys\_execve syscall) in the child process.
\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 simultaneouslly 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.
\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.
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.
\begin{table}[H]
\begin{tabular}{|>{\centering\arraybackslash}p{2cm}|>{\centering\arraybackslash}p{3cm}|}
\hline
\multicolumn{2}{|c|}{Original arguments}\\
\hline
\hline
filename & "/bin/ls"\\
\hline
argv[0] & "ls"\\
\hline
argv[1] & "-l"\\
\hline
argv[2] & NULL\\
\hline
envp[0] & NULL\\
\hline
\end{tabular}
\quad
\begin{tabular}{|>{\centering\arraybackslash}p{2cm}|>{\centering\arraybackslash}p{3cm}|}
\hline
\multicolumn{2}{|c|}{Modified arguments}\\
\hline
\hline
filename & "/home/osboxes/execve\_hijack"\\
\hline
argv[0] & "/bin/ls"\\
\hline
argv[1] & "-l"\\
\hline
argv[2] & NULL\\
\hline
envp[0] & NULL\\
\hline
\end{tabular}
\caption{Hiding data in sys\_execve arguments.}
\label{table:execve_args_hide}
\end{table}
As we can observe in the table, we will modify the value of \tetxit{filename} with the malicious program filename, and save the original filename into argv[0]. Performing this substitution means losing little information since the argv[0] argument contains the name of the program \cite{c_standard_main}, information that can also be taken from the filename (thus it can be recovered later). Only in very specific use cases the argv[0] argument is different from the file included in the filename argument (like in Busybox \cite{busybox_argv}).
After the above substitution, the malicious program (in the table, "execve\_hijack") will be called, whose main function receives the following arguments:
\begin{verbatim}
int main (int argc, char *argv[], char *envp[]){}
\end{verbatim}
Hence, the malicious program will use the argv[] and envp[] arrays to make another sys\_execve call with the original arguments, running the original program.
\subsection{Hijacking a program execution}
Once we have analysed the two fundamental issues regarding this module (bpf\_probe\_write\_user fails and hiding information in the syscall arguments) we will now analyze the execution hijacking module in detail using a sample program execution.
Figure

View File

@@ -114,9 +114,6 @@ hmargin=3cm
font=small,
}
%CUSTOM RULES
\chardef\_=`_
% FIGURES DESIGN
\usepackage{graphicx}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB