/eBPFeXPLOIT

Exploit tool implemented using ebpf.

Primary LanguageCMIT LicenseMIT

eBPFeXPLOIT

eBPFeXPLOIT is a penetration testing tool based on eBPF technology.

Current Features:

  • Hide its own PID and up to four other specified PIDs, with a maximum of five PIDs hidden for ease of processing.
  • eBPF memory horse (MemoryShell).
  • Block the Kill command. Processes with specified PIDs will not be terminated by Kill.
  • Hide injected eBPF programs, Maps, and Links in the kernel.

中文版本(Chinese version)

Hide Pid

Hide up to four target PIDs and its own PID, totaling five PIDs. By default, it hides its own PID.

go generate && go build -o main && ./main -pid 263959,269942

echo $$
263959
ps aux | grep -i "263959"
root      277863  0.0  0.0   3440  1920 pts/2    S+   13:51   0:00 grep --color=auto -i 263959

The principle of hiding PIDs lies in the getdents64 system call. In Linux, the getdents64 system call reads file information in a directory, and commands like ps use getdents64 to obtain process-related information from files in the /proc/ directory.

The second parameter of ctx is linux_dirent64 *dirp, structured as follows:

struct linux_dirent64 {
    u64        d_ino;    /* 64-bit inode number */
    u64        d_off;    /* 64-bit offset to next structure */
    unsigned short d_reclen; /* Size of this dirent */
    unsigned char  d_type;   /* File type */
    char           d_name[]; /* Filename (null-terminated) */ };

It represents the entries in the directory that getdents64 will access. The first two fields are less significant, the third indicates the length of the current linux_dirent64, and the fifth d_name is the filename of the target, e.g., for pid 200, it would be /proc/200, so d_name is 200.

By hooking this process and modifying the d_reclen of the previous linux_dirent64 to d_reclen_previous + d_reclen, the target PID's file can be skipped, thus hiding the PID.

However, due to complex logic, hiding too many PIDs can cause the verifier to fail, so including the program itself, a maximum of five can be hidden.

ebpf-MemoryShell

It can implement basic MemoryShell functionality, but there are a series of issues:

  • Temporary lack of handling for fragmented transmission.
  • My local Linux VM has issues, unable to properly configure tc to only receive egress traffic, resulting in tc receiving both egress and ingress traffic, which relatively decreases performance.
  • Command execution is in user space.
  • Command execution must be placed in the last parameter of get, as otherwise, it's too complex for eBPF kernel-side processing and fails to pass the verifier.
  • The original HTTP response byte count must be larger than the command execution result byte count for complete echo back. I tried expanding the HTTP response packet. It's possible to use bpf_skb_change_tail for expansion, but it's also necessary to modify the Content-Length value in the HTTP response header, which is very complex and fails to pass the verifier.

In terms of network packet processing performance, XDP is superior to TC, which is superior to hooking syscalls. Therefore, XDP is always the first choice, but XDP can only receive ingress traffic, while TC can receive egress traffic, so they are processed separately.

XDP sends the received commands to user space for execution, and then the user space sends the execution results back to TC, writing the results into the HTTP response. If the execution results are to be fully echoed back, an HTTP response with more bytes than the result is generally easy to find.

Although the dexec option is provided, the functionality of dexec=0 has not yet been implemented.

./main --help
Usage of ./main:
  -dexec string
        directly exec or not (default "-1")
  -ifname string
        interface xdp and tc will attach
  -pid string
        pid to hide (default "-1")

./main -ifname lo -dexec 1

image-20240109131903910

Prevent Kill

Even if the PID is hidden, if the operations team still somehow knows our program's PID, we need to prevent the Kill command from terminating our process.

First, hook lsm/task_kill, and when encountering a protected PID, return -EPERM to prevent subsequent execution.

Also, hook kretprobe/sys_kill, and when the syscall returns, modify the return value to -ESRCH to pretend the process does not exist:

go build -o main && ./main
2024/01/06 19:19:40 current pid:398235
2024/01/06 19:19:40 Waiting for events..

kill -9 398235
bash: kill: (398235) - No such process

I considered using kprobe or tp, but there was no good way to prevent subsequent processing at the time of entry, so I had to use lsm, but it doesn't seem like the best method.

I considered directly overwriting the return in kprobe, but I always had trouble correctly getting the PID parameter without using the BPF_KPROBE macro. It might be a problem with my VM, as I've been developing under a very high version of the Linux kernel and on arm64 architecture. It seems there are significant issues, and I need to find a way to develop remotely on an amd64 architecture Linux (since I'm developing eBPF on a Mac VM).

Hide eBPF Program

Although the user-space program's PID is hidden, commands like bpftool prog list can still discover our eBPF program injected into the kernel. However, considering that most Linux systems won't install bpftool, and operations might not even know what eBPF is, the likelihood of the injected eBPF program being discovered is very low. Therefore, this part of the process is relatively simple. Refer to the content in the book "Learning eBPF". Viewing progs or maps generally goes through the following process:

[0000ffffb38e1aa8] bpf(BPF_PROG_GET_NEXT_ID, {start_id=0, next_id=0, open_flags=0}, 12) = 0
[0000ffffb38e1aa8] bpf(BPF_PROG_GET_FD_BY_ID, {prog_id=2, next_id=0, open_flags=0}, 12) = 3
[0000ffffb38e1aa8] bpf(BPF_OBJ_GET_INFO_BY_FD, {info={bpf_fd=3, info_len=232, info=0xffffc95ef490}}, 16) = 0

First, use BPF_PROG_GET_NEXT_ID to get the ID, then BPF_PROG_GET_FD_BY_ID to get the fd, and finally BPF_OBJ_GET_INFO_BY_FD to get the related obj. This process repeats until BPF_PROG_GET_NEXT_ID can't find a related ID.

Therefore, directly hook the bpf syscall, and when encountering BPF_PROG_GET_NEXT_ID, BPF_MAP_GET_NEXT_ID, and BPF_LINK_GET_NEXT_ID, process accordingly.

How to Use

./main --help
Usage of ./main:
  -dexec string
        directly exec or not (default "-1")
  -ifname string
        interface xdp and tc will attach
  -pid string
        pid to hide (default "-1")

Hide other PIDs, by default, the program's own PID is hidden:

./main -pid 263959,269942

Memory horse:

./main -ifname lo -dexec 1

ifname specifies the network interface, set dexec to 1.

The current program only provides simple functionality, so after starting the program, press ctrl+c to stop it.

If the program encounters issues on tc and is not cleared, you can manually clear it:

tc qdisc del dev lo clsact

Replace 'lo' with your network interface as needed.

Prevent Kill (default functionality, for PIDs):

go build -o main && ./main
2024/01/06 19:19:40 current pid:398235
2024/01/06 19:19:40 Waiting for events..

kill -9 398235
bash: kill: (398235) - No such process

Hide eBPF program (hidden by default):

go build -o main && ./main

# All results are empty
bpftool prog list
bpftool map list
bpftool link list

The program uses newer features like BPF_MAP_TYPE_RINGBUF. I didn't check the minimum version requirements in detail, but according to GPT, it's roughly Kernel 5.8 and above.

So, it should work on Linux kernels version 5.8 and above. Additionally, the program needs to be run with root privileges.

The executable is cross-compiled with Go, theoretically executable on other architectures? This is my kernel version:

uname -a
Linux ubuntu-linux-22-04-02-desktop 6.5.13-060513-generic #202311281736 SMP PREEMPT_DYNAMIC Tue Nov 28 18:10:14 UTC 2023 aarch64 aarch64 aarch64 GNU/Linux

Manual Compilation


go generate
go build -o eBPFeXPLOIT

The compilation environment needs to be set up according to Getting Started - ebpf-go Documentation, as Go is used in user space. The vmlinux.h in the ebpf directory can be generated yourself:


bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h

For the version of clang, I fixed some bugs and currently support up to clang14. I haven't tested newer versions, but there probably won't be too many issues.

TODO

  • Currently, everything does not consider pinning the program and Map to the fs, only to provide convenient functionality. Once the overall functionality is almost implemented, pinning might be considered.
  • Indirect command execution, i.e., hooking execve for execution.
  • Compatibility with lower versions of Linux. I used many new features while writing the code, leading to incompatibility with lower versions of Linux kernels.
  • Add container escape functionality module.
  • Add backdoor functionality for sshd.

Disclaimer

If you commit any unlawful acts in the course of using the Tool, you are responsible for the consequences and we will not be liable for any legal and joint liability.

Unless you have fully read, fully understand and accept all the terms of this Agreement, please do not install and use the Tool. Your use of the behaviour or your acceptance of this Agreement in any other express or implied manner is deemed to have read and agreed to the constraints of this Agreement.

References

[译]使用os/exec执行命令

bpf-developer-tutorial/src/23-http/README.md at main · eunomia-bpf/bpf-developer-tutorial

使用cilium/ebpf编译并加载TC BPF代码

Gui774ume/ebpfkit: ebpfkit is a rootkit powered by eBPF

pathtofile/bad-bpf: A collection of eBPF programs demonstrating bad behavior, presented at DEF CON 29

Emulating Bad Networks

Routing Family Netlink Library (libnl-route)

Attaching EBPF program returns no such file or directory · Issue #32 · florianl/go-tc

tc package - github.com/florianl/go-tc - Go Packages

绿色记忆:eBPF学习笔记

Esonhugh/sshd_backdoor: /root/.ssh/authorized_keys evil file watchdog with ebpf tracepoint hook.