-
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
-
dd
the 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
nanosleep
syscall, and our code is executed. -
The payload I've written
open
s the file specified on the CLI, creates amemfd
, usessendfile
to copy the binary to memory, and then usesfexecve
to 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 wouldmmap
aPROT_EXEC
section, 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}
,SYSCALL
and aJMP [SOMETHING]
(I've usedRAX
for parity with syscalls) for syscalls and PLT calls
Files
memfdcreate.sh
- The actual payloadoverwrite.sh
- The entrypoint (bash overwrite.sh ./busybox-x86_64
)payload.sh
- Wrapper aroundmemfdcreate.sh
to create the payloadreadsyms.sh
- ELF Parserutils.sh
- Utility functions, including ROP generator
Features:
-
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
.text
section
Future work
-
Cache gadget offsets from the ASLR base on the first run, so the second run is faster
-
Interactivity with processes executed via the
fexecve
method. This can be achieved using FUSE'spassthrough
example, but this requireslibfuse
to be available.
Why this works:
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.
Binary dependencies
- Bash
- dd
- GNU grep (this can be worked around but it's slow)
Bash is the worst thing in the world
Bash does not handle binary data. ELF objects are binary data. This was 'fun'.