-
Launch
sleep -
Using
/proc/self/maps, find all required gadgets* in loaded libraries, if we can't find a gadget in automatically loaded libraries, find one anywhere in/usr/lib, and remember in which file it was found -
Execute a longer running sleep, if necessary adding the new libraries from 2 via
LD_PRELOAD -
Re-find the gadgets, with the correct ASLR offset, and add them to our payload, along with any data
-
ddthe payload directly over the stack, found in/proc/${PID}/maps, over/proc/${PID}/mem, pre-padded with NOPs -
Wait for sleep to return from the
nanosleepsyscall, and our code is executed. -
The payload I've written
opens the file specified on the CLI, creates amemfd, usessendfileto copy the binary to memory, and then usesfexecveto execute the in memory binary (which usesexecve(/proc/self/fd/X)under the hood). Any payload ROP payload is possible, previously I had a payload which wouldmmapaPROT_EXECsection, copy a standard shellcode file into memory and execute that, which itself mounted a FUSE filesystem. This method was more flexible but also seemingly more brittle, and had large amounts of handwritten ASM.
- We only actually need a
NOP,POP {RDI, RSI, RDX, RCX, R8, R9},SYSCALLand aJMP [SOMETHING](I've usedRAXfor parity with syscalls) for syscalls and PLT calls
memfdcreate.sh- The actual payloadoverwrite.sh- The entrypoint (bash overwrite.sh ./busybox-x86_64)payload.sh- Wrapper aroundmemfdcreate.shto create the payloadreadsyms.sh- ELF Parserutils.sh- Utility functions, including ROP generator
-
We can call any PLT (e.g glibc) function or syscall, with arbitrary arguments, including string arguments
-
Pure Bash ROPChain generator, including ELF parser to ensure grepped gadgets are within the
r-x.textsection
-
Cache gadget offsets from the ASLR base on the first run, so the second run is faster
-
Interactivity with processes executed via the
fexecvemethod. This can be achieved using FUSE'spassthroughexample, but this requireslibfuseto be available.
Parent processes can write to their children's /proc/${PID}/mem in most distros, due to the default value of /proc/sys/kernel/yama/ptrace_scope (1). The
less secure setting (0) allows for any process sharing a UID to write to another processes /proc/${PID}/mem.
To be the correct parent, we have to exec dd after we've generated the payload. This means that dd becomes the parent of sleep, but we then are unable to
execute something like wait to make the process interactive.
- Bash
- dd
- GNU grep (this can be worked around but it's slow)
Bash does not handle binary data. ELF objects are binary data. This was 'fun'.