Running arbitrary ELFs on noexec,nosuid,nodev mounts – without ever whispering execve(2) to the kernel.
A straight-from-the-underground deep dive into the userland-exec toolkit. We built it. We drop it. We own the box.
The Radical Threat Model (because “defense in depth” is just marketing until you test it)
Sysadmins think they’ve won when they slap this on:
mount -t tmpfs -o noexec,nosuid,nodev tmpfs /tmp/noexec_demo
Add SELinux enforcing or a tight AppArmor profile and they call it a day.
But the real threat model – the one the underground has respected since the grugq days – is brutally simple:
Attacker launches exploit
|
v
+-------------------------+
| Remote code execution |
| (stack smash, fmtstr, |
| use-after-free...) |
+-------------------------+
|
v
Target: Hardened Linux Box
┌─────────────────────────────────────┐
│ • SELinux (enforcing) or AppArmor │
│ • /tmp mounted noexec,nosuid,nodev │
│ • Full modern mitigations │
└─────────────────────────────────────┘
|
v
Process pwned -- arbitrary code exec
|
v
USERLAND-EXEC TAKES OVER
┌─────────────────────────────────────┐
│ • Parse ELF in userspace │
│ • mmap / memfd_create segments │
│ • W^X signal handler dance │
│ • Direct jmp to e_entry │
└─────────────────────────────────────┘
|
v
Stealthy RCE achieved.
Original process name & context intact.
No execve(2) ever called. No MAC hooks.
noexec? What noexec.
May your userland-exec payload not nuke your own cyber bunker.
This is exactly the scenario laid out in the
grsecurity-101 threat model documentation: once arbitrary code execution is achieved inside a process, filesystem-based and execve-centric controls become largely theater. The kernel never gets a chance to enforce noexec because the binary never travels through the kernel’s execution path.
We took that reality, turned it into a clean, reusable library, and wrapped it in a realistic RCE demo so you can watch the bypass happen live.
How Userland Exec Actually Works (high-level, no handwaving)
Forget handing the kernel the crown via execve. Our userland_execv() does it the cypherpunk way – entirely in userspace:
- Parses the target ELF header and program segments (
PT_LOAD) mmap()s (ormemfd_create+mmap) every segment straight into the current address space- Handles relocations and dynamic linking when the payload needs it
- Transfers control with a direct jump to the new ELF’s entry point
Result? The process image is replaced in place. ps, top, /proc/<pid>/comm still show the original binary name. No new process, no audit trail that screams “new exec”.
Userland-Exec Internals (the gory bits)
Everything lives in src/. It’s a full in-process ELF loader built for hostile environments. Highlights:
- ELF parsing & loading: Manual walk through program headers, no libc hand-holding.
- Memory mapping:
mmap()ormemfd_create+mmapchains – zero reliance on the kernel’s exec path. - W^X bypass (
bypass_wx.c): Map pagesPROT_EXEC, catchSIGSEGVon write attempts, flip toPROT_WRITEtemporarily, restorePROT_EXECafter init. Beautiful signal-handler kung-fu that laughs atexecmemdenials. - SELinux / AppArmor dodging: Skip
mprotect()entirely when possible.memfd_create+munmap+mmapflows keep the policy engine in the dark. - Control transfer: Straight
jmptoe_entry. Process context (UID, open fds, etc.) preserved.
This is why the technique eats hardened boxes for breakfast.
The rce_demo: Real-World Attack Chain
We shipped a deliberately vulnerable TCP server (victim_server.c) with:
- Format-string leak in the banner (bye-bye ASLR)
- Classic stack buffer overflow (
recv()into 64-byte buffer, RIP smash at offset 72)
Linked against libuserland-exec.a, compiled with mitigations turned off for clarity (you’ll re-enable them in real ops).
Exploit flow (exploit.py does it all)
- Offline recon (
nm,objdump) for gadgets and offsets. - Format-string infoleak → full ASLR defeat.
- ROP chain (ret +
jmp rspfrom libc) to pivot to stack shellcode. - Ship the ELF payload over the wire.
Shellcode modes:
Disk mode (for when you don’t care about traces):
fd = open("/tmp/noexec_rce_demo/payload", O_WRONLY|O_CREAT, 0755);
write(fd, payload, size);
userland_execv(fd); // noexec? what noexec?
Memfd mode (the real stealth play):
memfd = memfd_create("stealth_payload", 0);
write(memfd, payload, size);
lseek(memfd, 0, SEEK_SET);
userland_execv(memfd); // zero on-disk footprint
The demo payload just phones home:
printf("[+] Userland RCE successful!\n");
printf(" PID : %d\n UID : %d\n Kernel : %s\n Hostname: %s\n",
getpid(), getuid(), read_file("/proc/version"), read_file("/proc/sys/kernel/hostname"));
Setup Instructions (VM only, kids)
git clone https://github.com/hardenedlinux/userland-exec.git
cd userland-exec
mkdir build && cd build
cmake ..
make -j$(nproc)
Victim side:
sudo ./rce_demo/setup.sh # drops the noexec tmpfs
./build/rce_demo/victim_server # listens on 4444
Pwn it:
python3 rce_demo/exploit.py # local test
python3 rce_demo/exploit.py --target <ip> # remote
python3 rce_demo/exploit.py --target <ip> --mode memfd # ghost mode

Cleanup:
sudo ./rce_demo/setup.sh cleanup
Requirements: x86_64 Linux, CMake, GCC, binutils, Python 3.10+, root on victim for the tmpfs.
Stealth & Bypass Scorecard
- noexec: 100% evaded. Never hits the kernel check.
- SELinux / AppArmor: Most execve-tied hooks skipped.
memfd+ signal W^X laughs atexecmem. - Stealth: Original comm name intact. Memfd mode = zero filesystem artifacts.
psandtoplie for you.
Limitations (we keep it real)
Demo disables ASLR/PIE/stack canaries/etc. so you can see the technique clearly. Real engagements need a proper initial vector + extra bypasses for modern mitigations. But the core primitive remains lethal once you’ve got code exec.
Mitigations?
- PaX/GRsecurity TPE + RBAC – the only thing that actually stops this class of attack.
- Strict seccomp filters that clamp
memfd_create, suspiciousmmappatterns, and signal abuse. - Run services in the tightest possible DAC/MAC sandbox with minimal caps.
- Layered defense > single silver bullets like
noexec. - FFI is the beast forever, the old legacy from GRHack
Conclusion
The userland-exec project (and its rce_demo) is proof that once arbitrary code execution lands inside a process, most of the “hardened Linux” playbook turns into expensive theater. Noexec, MAC policies, filesystem controls – all bypassed in userspace with zero kernel involvement.
Clone it. Build it. Run the demo on your “secure” box. Then ask yourself how many of your production systems would survive the same treatment.
https://github.com/hardenedlinux/userland-exec
May your userland-exec payload never nuke your own cyber bunker.
Stay dangerous. – the crew
Tags: linux pwnage, noexec bypass, userland exec, stealthy RCE, SELinux, AppArmor, red team, grsecurity, oldskool hacking, W^X dance