From a1815ab7fe223b71f8814bf5a63cf924935e50f8 Mon Sep 17 00:00:00 2001 From: Dobin Rutishauser Date: Sun, 16 Jun 2024 08:28:20 +0200 Subject: [PATCH] feature: in-place dll loader (support) --- pe/pehelper.py | 28 ++++++++++++++++++++++++++++ phases/injector.py | 8 +++++++- supermega.py | 24 ++++++++++++------------ 3 files changed, 47 insertions(+), 13 deletions(-) diff --git a/pe/pehelper.py b/pe/pehelper.py index 7f11ca8..ff8df78 100644 --- a/pe/pehelper.py +++ b/pe/pehelper.py @@ -11,6 +11,26 @@ logger = logging.getLogger("PEHelper") # Its mostly used for verification of what we were doing. +# PRE-LOAD a dll file into memory +# This will load the DLL file into a memory buffer, already +# loaded at the correct RVA addresses (e.g. sections page aligned). +def preload_dll(payload_path: str) -> bytes: + dllPe = pefile.PE(payload_path) + dllImageSize = dllPe.OPTIONAL_HEADER.SizeOfImage + payload: bytearray = bytearray(dllImageSize) + + # copy PE header sizeofheaders + payload[:dllPe.OPTIONAL_HEADER.SizeOfHeaders] = dllPe.get_data()[:dllPe.OPTIONAL_HEADER.SizeOfHeaders] + + # copy sections + for section in dllPe.sections: + if section.SizeOfRawData == 0: + continue + payload[section.VirtualAddress:section.VirtualAddress + section.SizeOfRawData] = section.get_data() + + return bytes(payload) + + def extract_code_from_exe_file_ep(exe_file: FilePath, len: int) -> bytes: pe = pefile.PE(exe_file) section = get_code_section(pe) @@ -69,3 +89,11 @@ def remove_trailing_null_bytes(data: bytes) -> bytes: 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 align_to_page_size(rva, offset, page_size=4096): + # Align to the nearest lower page boundary + aligned_address = rva & ~(page_size - 1) + real_address = aligned_address - offset + logger.debug(" Aligning: 0x{:X} to 0x{:X}".format(aligned_address, real_address)) + return real_address \ No newline at end of file diff --git a/phases/injector.py b/phases/injector.py index a34f5cd..2902d1c 100644 --- a/phases/injector.py +++ b/phases/injector.py @@ -17,7 +17,7 @@ from model.defs import * logger = logging.getLogger("Injector") -def inject_exe(main_shc: bytes, settings: Settings, carrier: Carrier): +def inject_exe(main_shc: bytes, settings: Settings, carrier: Carrier, project: Project): exe_in = settings.inject_exe_in exe_out = settings.inject_exe_out carrier_invoke_style: CarrierInvokeStyle = settings.carrier_invoke_style @@ -90,6 +90,12 @@ def inject_exe(main_shc: bytes, settings: Settings, carrier: Carrier): shellcode_offset += sect.PointerToRawData shellcode_rva = superpe.pe.get_rva_from_offset(shellcode_offset) + # Aligning the payload (not carrier!) to page size is important for dll_loader_change + if settings.carrier_name == "dll_loader_change": + # align shellcode_rva minus an offset to page size + shellcode_rva = align_to_page_size(shellcode_rva, shellcode_len - project.payload.len) + shellcode_offset = superpe.pe.get_offset_from_rva(shellcode_rva) + logger.info("---( Inject: Write Shellcode to offset:0x{:X} (rva:0x{:X})".format( shellcode_offset, shellcode_rva)) diff --git a/supermega.py b/supermega.py index 84105e1..a426605 100644 --- a/supermega.py +++ b/supermega.py @@ -12,13 +12,13 @@ import phases.compiler import phases.assembler import phases.injector from observer import observer -from pe.pehelper import extract_code_from_exe_file_ep +from pe.pehelper import preload_dll from sender import scannerDetectsBytes from model.project import Project, prepare_project from model.settings import Settings from model.defs import * from log import setup_logging -from model.carrier import Carrier, DataReuseEntry, IatRequest +from model.carrier import DataReuseEntry def main(): @@ -151,6 +151,14 @@ def start_real(settings: Settings): project.settings.plugin_decoy) ) + # FIXUP DLL Payload + # Prepare DLL payload for usage in dll_loader_change + # This needs to be done before rendering the C templates, as the need + # the size of the payload + if project.settings.carrier_name == "dll_loader_change": + project.payload.payload_data = preload_dll(project.payload.payload_path) + project.payload.len = len(project.payload.payload_data) + # CREATE: Carrier C source files from template (C->C) phases.templater.create_c_from_template(settings, project.payload.len) @@ -198,19 +206,11 @@ def start_real(settings: Settings): #observer.add_code_file("full_shc", full_shellcode) else: # shellcode is in .rdata, so we dont need to merge + # This is handle before, e.g. encoding. full_shellcode = carrier_shellcode - # RWX Injection (optional): obfuscate loader+payload - #if project.exe_host.rwx_section != None: - # logger.info("--[ RWX section {} found. Will obfuscate loader+payload and inject into it".format( - # project.exe_host.rwx_section.Name.decode().rstrip('\x00') - # )) - # obfuscate_shc_loader(settings.main_shc_path, settings.main_shc_path + ".sgn") - # observer.add_code_file("payload_sgn", file_readall_binary(settings.main_shc_path + ".sgn")) - # shutil.move(settings.main_shc_path + ".sgn", settings.main_shc_path) - # inject (merged) loader into an exe. Big task. - phases.injector.inject_exe(full_shellcode, settings, project.carrier) + phases.injector.inject_exe(full_shellcode, settings, project.carrier, project) #observer.add_code_file("exe_final", extract_code_from_exe_file_ep(settings.inject_exe_out, 300)) # Check binary with avred