# tl; dr I sometimes operate in restricted Linux environments, where dropping and running an ELF executable implant may be undesirable due to EDRs/monitoring/other environment restrictions. One solution is to drop a shared library and load it, but I don't always need to load a fully-fledged implant. So, I decided to use a simple .sh loader for a bash builtin .so that would allow me to extend a simple reverse shell/SSH session with arbitrary native functionality without loading a full implant. This project could be considered a logical opposite of https://github.com/cmprmsd/BusyBOF/ -- `BusyBOF` extends a native implant with functionality similar to a regular Linux shell environment, while `powerup` extends a regular shell with functionality that is typically present in Linux implants. Available on [Github](https://github.com/zimnyaa/pwrup) ![[Pasted image 20260422103931.png]] *I spent the tokens so you don't have to* # architecture Bash allows loading custom builtins from shared objects with `enable -f` that are then available as regular builtin commands. The `pwr` extension is written in Zig and exposes a single command with subcommands that execute various tasks. > **note:** This **will not work** on strictly locked-down systems where `dlopen` requires the file to be signed and a different approach should be used there. Also, bash loading an unknown shared library is not typical and may be an IoC in your environment specifically (though in practice it rarely is). The .sh loader is very simple: ``` find a temporary location without `noexec` -> create a file, open an fd to it, and delete it -> write the .so to the fd -> enable -f /proc/<PID>/fd/<FD> ``` > **note:** If I was a Linux EDR, I would paint the entire web console red for a single `dlopen()` from `/proc/PID/fd` The PoC includes some native functionality that may be missing on a typical host (a lot of stuff could be done with just bash and coreutils): - a simple `tcpdump` implementation - renaming the bash process - loading and executing an arbitrary export from a .so - hunting for byte patterns in process memory (although that could be done with just coreutils) - `strace` for open/read/write/close All of the above was shamelessly vibecoded, since the functionality is pretty generic. # usage ``` zig build embed -Doptimize=ReleaseSmall # creates pwr.sh in ./zig-out . ./pwr.sh # or eval it pwr <subcommand> <arguments> ``` Tested on x86-64 and aarch64 only.