From 055d74c22d876a019f0017106ee83e9d88c6be5d Mon Sep 17 00:00:00 2001 From: Dobin Rutishauser Date: Fri, 30 Jan 2026 14:33:44 +0100 Subject: [PATCH] feature: defender antimalwareengine events --- RedEdr/RedEdr.cpp | 9 ++-- RedEdr/config.h | 5 +- RedEdr/etwreader.cpp | 107 ++++++++++++++++++++++++++----------- RedEdr/etwreader.h | 2 +- RedEdr/event_processor.cpp | 43 ++++++++------- RedEdr/shared.js | 6 +-- RedEdr/webserver.cpp | 19 +++---- RedEdrShared/etw_krabs.cpp | 3 +- RedEdrShared/utils.cpp | 10 ++++ RedEdrShared/utils.h | 1 + rededr_test.ps1 | 56 +++++++++++++++++++ rededrtest.ps1 | 52 ------------------ 12 files changed, 187 insertions(+), 126 deletions(-) create mode 100644 rededr_test.ps1 delete mode 100644 rededrtest.ps1 diff --git a/RedEdr/RedEdr.cpp b/RedEdr/RedEdr.cpp index 0b87fcd..213f931 100644 --- a/RedEdr/RedEdr.cpp +++ b/RedEdr/RedEdr.cpp @@ -64,7 +64,8 @@ int main(int argc, char* argv[]) { ("k,hook", "Input: Kernel and ntdll hooks", cxxopts::value()->default_value("false")) // Input options - ("with-unfiltered-etw", "Input option: Enable unfiltered ETW (performance impact)", cxxopts::value()->default_value("false")) + ("with-defendertrace", "Input option Defender: Add MsMpEng.exe access events to target process", cxxopts::value()->default_value("false")) + ("with-antimalwareengine", "Input option Defender: Grab events of ETW Microsoft-Antimalware-Engine related to target process", cxxopts::value()->default_value("false")) // Output ("w,web", "Output: Web server", cxxopts::value()->default_value("true")) @@ -123,11 +124,11 @@ int main(int argc, char* argv[]) { g_Config.debug_dllreader = result["dllreader"].as(); g_Config.hide_full_output = ! result["show"].as(); g_Config.web_output = result["web"].as(); - g_Config.disable_unfiltered_etw = ! result["with-unfiltered-etw"].as(); - //g_Config.do_dllinjection_ucallstack = result["dllcallstack"].as(); + g_Config.do_defendertrace = result["with-defendertrace"].as(); + g_Config.do_antimalwareengine = result["with-antimalwareengine"].as(); if (!g_Config.do_etw && !g_Config.do_hook && !g_Config.do_etwti && !g_Config.debug_dllreader) { - printf("Choose at least one of --etw --etwti --hook"); + printf("Choose at least one of --etw / --etwti / --hook"); return 1; } diff --git a/RedEdr/config.h b/RedEdr/config.h index 0890227..f6bfe99 100644 --- a/RedEdr/config.h +++ b/RedEdr/config.h @@ -21,12 +21,15 @@ public: bool do_udllinjection = false; bool debug_dllreader = false; bool enable_remote_exec = true; - bool disable_unfiltered_etw = false; // Input selection bool do_etw = false; bool do_etwti = false; bool do_hook = false; + + // More input + bool do_defendertrace = false; + bool do_antimalwareengine = false; bool do_dllinjection_ucallstack = true; // ETW input selection diff --git a/RedEdr/etwreader.cpp b/RedEdr/etwreader.cpp index ddeb3fa..2e92299 100644 --- a/RedEdr/etwreader.cpp +++ b/RedEdr/etwreader.cpp @@ -10,24 +10,27 @@ #include "etwreader.h" #include "process_resolver.h" #include "config.h" +#include "utils.h" krabs::user_trace trace_user(L"RedEdrUser"); -BOOL use_additional_etw = FALSE; +BOOL is_trace_in_progress = FALSE; // currently unused HANDLE threadReadynessEtw = NULL; // ready to start tracing -void enable_additional_etw(BOOL use) { - use_additional_etw = use; + +void trace_in_progress(BOOL use) { + is_trace_in_progress = use; } -void event_callback(const EVENT_RECORD& record, const krabs::trace_context& trace_context) { +// Handle ETW events for process monitoring +// - Where ProcessId of EventHeader is our target process +void event_callback_process(const EVENT_RECORD& record, const krabs::trace_context& trace_context) { try { krabs::schema schema(record, trace_context.schema_locator); - - // This function(-chain) should be high performance, or we lose events. - + + // Check if we observe the process which emitted this event DWORD processId = record.EventHeader.ProcessId; Process* process = g_ProcessResolver.getObject(processId); if (process == NULL) { @@ -37,9 +40,12 @@ void event_callback(const EVENT_RECORD& record, const krabs::trace_context& trac if (!process->observe) { return; } + + // Convert ETW to JSON nlohmann::json j = KrabsEtwEventToJsonStr(record, schema); - j["process_name"] = process->name; - j["pid"] = processId; + j["etw_process"] = process->name; + + // Emit event g_EventAggregator.NewEvent(j.dump()); } catch (const std::exception& e) { @@ -51,28 +57,67 @@ void event_callback(const EVENT_RECORD& record, const krabs::trace_context& trac } -void event_callback_nofilter(const EVENT_RECORD& record, const krabs::trace_context& trace_context) { - if (!use_additional_etw) { - // If we dont use additional ETW, we dont want to process these events - return; - } +// Handle ETW events for antimalware monitoring +// - Where "pid" or "filename" or "name" matches our target processes +void event_callback_antimalware(const EVENT_RECORD& record, const krabs::trace_context& trace_context) { + //if (!is_trace_in_progress) { + // return; + //} try { krabs::schema schema(record, trace_context.schema_locator); + // Convert ETW to JSON + nlohmann::json j = KrabsEtwEventToJsonStr(record, schema); - // This function(-chain) should be high performance, or we lose events. + // Resolve (source) process name DWORD processId = record.EventHeader.ProcessId; Process* process = g_ProcessResolver.getObject(processId); if (process == NULL) { LOG_A(LOG_WARNING, "ETW: No process object for pid %lu", processId); return; } + j["etw_process"] = process->name; - // This will get information about the process, which may be slow, if not - // done before. It can be done before, e.g. when Kernel event arrived - nlohmann::json j = KrabsEtwEventToJsonStr(record, schema); - j["process_name"] = process->name; - g_EventAggregator.NewEvent(j.dump()); + // Check if destination is one of our target processes + if (j.contains("pid") && !j["pid"].is_null()) { + DWORD targetPid = j["pid"].get(); + + // check if we observe the target process + Process* targetProcess = g_ProcessResolver.getObject(targetPid); + if (targetProcess == NULL) { + LOG_A(LOG_WARNING, "ETW: No target process object for pid %lu", targetPid); + return; + } + if (targetProcess->observe) { + // Emit event + g_EventAggregator.NewEvent(j.dump()); + } + } + // Check if filename matches any of our target processes + // Cache MOACLookup 36 + else if (j.contains("filename") && !j["filename"].is_null()) { + std::string filename = j["filename"].get(); + for (const auto& targetProcessName : g_Config.targetProcessNames) { + if (ends_with_case_insensitive(filename, targetProcessName)) { + // Emit event + g_EventAggregator.NewEvent(j.dump()); + break; + } + } + } + // Check if name matches any of our target processes + // ExpensiveOperationTaskExpensiveOperationBegin 43 + // ExpensiveOperationTaskExpensiveOperationEnd 67 + else if (j.contains("name") && !j["name"].is_null()) { + std::string filename = j["name"].get(); + for (const auto& targetProcessName : g_Config.targetProcessNames) { + if (ends_with_case_insensitive(filename, targetProcessName)) { + // Emit event + g_EventAggregator.NewEvent(j.dump()); + break; + } + } + } } catch (const std::exception& e) { LOG_A(LOG_ERROR, "ETW event_callback exception: %s", e.what()); @@ -117,7 +162,7 @@ DWORD WINAPI TraceProcessingThread(LPVOID param) { 1 ProcessStart 2 ProcessStop 3 ThreadStart - 4 ThreadStop? + 4 ThreadStop 5 ImageLoad 6 ImageUnload 11 ProcessFreeze @@ -126,7 +171,7 @@ DWORD WINAPI TraceProcessingThread(LPVOID param) { std::vector process_event_ids = { 1, 2, 3, 4, 5, 6, 11 }; krabs::event_filter process_filter(process_event_ids); process_provider.trace_flags(process_provider.trace_flags() | EVENT_ENABLE_PROPERTY_STACK_TRACE); - process_filter.add_on_event_callback(event_callback); + process_filter.add_on_event_callback(event_callback_process); process_provider.add_filter(process_filter); trace_user.enable(process_provider); LOG_A(LOG_INFO, "ETW: Microsoft-Windows-Kernel-Process (1, 2, 3, 4, 5, 6, 11)"); @@ -147,7 +192,7 @@ DWORD WINAPI TraceProcessingThread(LPVOID param) { std::vector auditapi_event_ids = { 3, 4, 5, 6 }; krabs::event_filter auditapi_filter(auditapi_event_ids); auditapi_provider.trace_flags(auditapi_provider.trace_flags() | EVENT_ENABLE_PROPERTY_STACK_TRACE); - auditapi_filter.add_on_event_callback(event_callback); + auditapi_filter.add_on_event_callback(event_callback_process); auditapi_provider.add_filter(auditapi_filter); trace_user.enable(auditapi_provider); LOG_A(LOG_INFO, "ETW: Microsoft-Windows-Kernel-Audit-API-Calls (3, 4, 5, 6)"); @@ -174,7 +219,7 @@ DWORD WINAPI TraceProcessingThread(LPVOID param) { std::vector kernelfile_event_ids = { 10, 30 }; krabs::event_filter kernelfile_filter(kernelfile_event_ids); kernelfile_provider.trace_flags(kernelfile_provider.trace_flags() | EVENT_ENABLE_PROPERTY_STACK_TRACE); - kernelfile_filter.add_on_event_callback(event_callback); + kernelfile_filter.add_on_event_callback(event_callback_process); kernelfile_provider.add_filter(kernelfile_filter); trace_user.enable(kernelfile_provider); LOG_A(LOG_INFO, "ETW: Microsoft-Windows-Kernel-File (10, 30)"); @@ -194,7 +239,7 @@ DWORD WINAPI TraceProcessingThread(LPVOID param) { std::vector kernelnetwork_event_ids = { 12, 15, 28, 31, 42, 43, 58, 59 }; krabs::event_filter kernelnetwork_filter(kernelnetwork_event_ids); kernelnetwork_provider.trace_flags(kernelnetwork_provider.trace_flags() | EVENT_ENABLE_PROPERTY_STACK_TRACE); - kernelnetwork_filter.add_on_event_callback(event_callback); + kernelnetwork_filter.add_on_event_callback(event_callback_process); kernelnetwork_provider.add_filter(kernelnetwork_filter); trace_user.enable(kernelnetwork_provider); LOG_A(LOG_INFO, "ETW: Microsoft-Windows-Kernel-Network (12, 15, 28, 31, 42, 43, 58, 59)"); @@ -242,15 +287,15 @@ DWORD WINAPI TraceProcessingThread(LPVOID param) { /* krabs::provider<> securityauditing_provider(L"Microsoft-Windows-Security-Auditing"); securityauditing_provider.trace_flags(securityauditing_provider.trace_flags() | EVENT_ENABLE_PROPERTY_STACK_TRACE); - securityauditing_provider.add_on_event_callback(event_callback); + securityauditing_provider.add_on_event_callback(event_callback_process); trace_user.enable(securityauditing_provider); */ - // Microsoft-Windows-Threat-Intelligence - /* everything - for the duration of use_additional_etw = true */ - if (! g_Config.disable_unfiltered_etw) { - krabs::provider<> antimalwareengine_provider(L"Microsoft-Antimalware-Engine"); - antimalwareengine_provider.add_on_event_callback(event_callback_nofilter); + // Microsoft-Antimalware-Engine + // We currently observe all event id's, and filter in the callback + krabs::provider<> antimalwareengine_provider(L"Microsoft-Antimalware-Engine"); + if (g_Config.do_antimalwareengine) { + antimalwareengine_provider.add_on_event_callback(event_callback_antimalware); trace_user.enable(antimalwareengine_provider); LOG_A(LOG_INFO, "ETW: Microsoft-Antimalware-Engine (all)"); } diff --git a/RedEdr/etwreader.h b/RedEdr/etwreader.h index 54bfb56..094a99e 100644 --- a/RedEdr/etwreader.h +++ b/RedEdr/etwreader.h @@ -11,4 +11,4 @@ BOOL InitializeEtwReader(std::vector& threads); void EtwReaderStopAll(); BOOL WINAPI ConsoleCtrlHandler(DWORD ctrlType); DWORD WINAPI TraceProcessingThread(LPVOID param); -void enable_additional_etw(BOOL use); \ No newline at end of file +void trace_in_progress(BOOL use); \ No newline at end of file diff --git a/RedEdr/event_processor.cpp b/RedEdr/event_processor.cpp index 6577ee7..88b76e6 100644 --- a/RedEdr/event_processor.cpp +++ b/RedEdr/event_processor.cpp @@ -115,31 +115,34 @@ void EventProcessor::AnalyzeEventJson(nlohmann::json& j) { LOG_A(LOG_WARNING, "No type? %s", j.dump().c_str()); return; } - if (!j.contains("pid")) { - LOG_A(LOG_WARNING, "No pid? %s", j.dump().c_str()); - return; - } + //if (!j.contains("pid")) { + // LOG_A(LOG_WARNING, "No pid? %s", j.dump().c_str()); + // return; + //} // Stats (for UI) EventStats(j); - Process* process = g_ProcessResolver.getObject(j["pid"].get()); - if (process == nullptr) { - // Should not happen - LOG_A(LOG_WARNING, "EventProcessor: Failed to get process object for pid %lu", j["pid"].get()); - return; - } + // etw_pid is typically the source process of the event + if (j.contains("etw_pid") && !j["etw_pid"].is_null()) { + Process* process = g_ProcessResolver.getObject(j["etw_pid"].get()); + if (process == nullptr) { + // Should not happen + LOG_A(LOG_WARNING, "EventProcessor: Failed to get process object for pid %lu", j["etw_pid"].get()); + return; + } - // Check if we need to gather the detailed information about the process - // If yes (not done before), do it and log it - if (! process->augmented) { - process->AugmentInfo(); - process->augmented = true; - LogInitialProcessInfo(process); - } + // Check if we need to gather the detailed information about the process + // If yes (not done before), do it and log it + if (!process->augmented) { + process->AugmentInfo(); + process->augmented = true; + LogInitialProcessInfo(process); + } - // Augment the JSON Event with memory info - AugmentEventWithMemAddrInfo(j, process); + // Augment the JSON Event with memory info + AugmentEventWithMemAddrInfo(j, process); + } // Print Event PrintEvent(j); @@ -214,7 +217,7 @@ void EventProcessor::EventStats(nlohmann::json& j) { num_dll += 1; } else if (j["type"] == "etw") { - if (j["provider_name"] == "Microsoft-Windows-Threat-Intelligence") { + if (j["etw_provider_name"] == "Microsoft-Windows-Threat-Intelligence") { num_etwti += 1; } else { diff --git a/RedEdr/shared.js b/RedEdr/shared.js index 8be88fb..e6b94d3 100644 --- a/RedEdr/shared.js +++ b/RedEdr/shared.js @@ -20,9 +20,9 @@ function displayEvents(events) { } // header - if (key === 'time' || key === 'pid' || key === 'tid' || - key === 'krn_pid' || key === 'ppid' || key === 'observe' || - key === 'thread_id' || key === 'provider_name' || key === 'id' || key == 'trace_id' + if (key === 'etw_time' || key === 'etw_pid' || key === 'etw_process' || key === 'etw_tid' || + key === 'etw_pid' || key === 'etw_event_id' || + key === 'thread_id' || key === 'etw_provider_name' || key === 'id' || key == 'trace_id' ) { eventHeader += `${key}:${value} `; } else if (key === 'type' || key === 'func' || key === 'event' || key === 'task') { diff --git a/RedEdr/webserver.cpp b/RedEdr/webserver.cpp index 3fbff5c..0d8d73c 100644 --- a/RedEdr/webserver.cpp +++ b/RedEdr/webserver.cpp @@ -199,7 +199,6 @@ DWORD WINAPI WebserverThread(LPVOID param) { ] */ try { - res.set_content(g_EventProcessor.GetAllAsJson(), "application/json"); } catch (const std::exception& e) { LOG_A(LOG_ERROR, "Error getting events: %s", e.what()); @@ -245,7 +244,7 @@ DWORD WINAPI WebserverThread(LPVOID param) { auto data = json::parse(req.body); if (data.contains("trace")) { if (! data["trace"].is_array()) { - LOG_A(LOG_ERROR, "Targets should be an array"); + LOG_A(LOG_ERROR, "Trace start: Targets should be an array, but is %s", data["trace"]); json error_response = { {"error", "trace should be an array"} }; res.status = 400; res.set_content(error_response.dump(), "application/json"); @@ -259,19 +258,13 @@ DWORD WINAPI WebserverThread(LPVOID param) { ManagerApplyNewTargets(); /* - // enable Nofilter ETW (experimental) std::string use_additional_etw; if (req.has_file("use_additional_etw")) { auto use_additional_etw_field = req.get_file_value("use_additional_etw"); use_additional_etw = use_additional_etw_field.content; } - if (use_additional_etw == "true") { - enable_additional_etw(true); - } - else { - enable_additional_etw(false); - } */ + trace_in_progress(true); json response = { {"result", "ok"} }; res.set_content(response.dump(), "application/json"); @@ -289,13 +282,13 @@ DWORD WINAPI WebserverThread(LPVOID param) { } }); svr.Post("/api/trace/reset", [](const httplib::Request&, httplib::Response& res) { -/* - enable_additional_etw(false); -*/ - + trace_in_progress(false); g_EventAggregator.ResetData(); g_EventProcessor.ResetData(); }); + svr.Post("/api/trace/stop", [](const httplib::Request&, httplib::Response& res) { + trace_in_progress(false); + }); // Lock management endpoints svr.Post("/api/lock/acquire", [](const httplib::Request&, httplib::Response& res) { diff --git a/RedEdrShared/etw_krabs.cpp b/RedEdrShared/etw_krabs.cpp index 92d140e..f6498ad 100644 --- a/RedEdrShared/etw_krabs.cpp +++ b/RedEdrShared/etw_krabs.cpp @@ -30,7 +30,7 @@ nlohmann::json KrabsEtwEventToJsonStr(const EVENT_RECORD& record, krabs::schema j["event"] = d; //j["opcode_id"] = schema.event_opcode(); - j["event_id"] = schema.event_id(); + j["etw_event_id"] = schema.event_id(); // The ProviderId is just the UID of the provider, which is not very useful // This is a workaround. Alternative would be to use TdhGetEventInformation()? @@ -52,6 +52,7 @@ nlohmann::json KrabsEtwEventToJsonStr(const EVENT_RECORD& record, krabs::schema continue; } std::string jsonKey = wstring2string((std::wstring&)propertyName); + std::transform(jsonKey.begin(), jsonKey.end(), jsonKey.begin(), ::tolower); // lowercase // Special cases if (propertyName == L"ProtectionMask" || propertyName == L"LastProtectionMask") { diff --git a/RedEdrShared/utils.cpp b/RedEdrShared/utils.cpp index c807ce0..6a16a1d 100644 --- a/RedEdrShared/utils.cpp +++ b/RedEdrShared/utils.cpp @@ -118,6 +118,16 @@ bool contains_case_insensitive(const std::string& haystack, const std::string& n } +bool ends_with_case_insensitive(const std::string& str, const std::string& suffix) { + if (suffix.size() > str.size()) { + return false; + } + std::string str_lower = to_lowercase2(str); + std::string suffix_lower = to_lowercase2(suffix); + return str_lower.compare(str_lower.size() - suffix_lower.size(), suffix_lower.size(), suffix_lower) == 0; +} + + // Dear mother of god whats up with all these goddamn string types wchar_t* string2wcharAlloc(const std::string& str) { if (str.empty()) { diff --git a/RedEdrShared/utils.h b/RedEdrShared/utils.h index 3bb5299..3767b44 100644 --- a/RedEdrShared/utils.h +++ b/RedEdrShared/utils.h @@ -30,6 +30,7 @@ std::string wchar2string(const wchar_t* wstr); // 4 std::wstring string2wstring(const std::string& str); // 3 bool contains_case_insensitive(const std::string& haystack, const std::string& needle); // 5 +bool ends_with_case_insensitive(const std::string& str, const std::string& suffix); // 1 void remove_all_occurrences_case_insensitive(std::string& str, const std::string& to_remove); // 3 bool wstring_starts_with(const std::wstring& str, const std::wstring& prefix); // 3 wchar_t* JsonEscape(wchar_t* str, size_t buffer_size); // 9 diff --git a/rededr_test.ps1 b/rededr_test.ps1 new file mode 100644 index 0000000..be5477e --- /dev/null +++ b/rededr_test.ps1 @@ -0,0 +1,56 @@ +# RedEdr Test Script +# Starts RedEdr, traces procexp64.exe, then cleans up + +$rededrPath = "C:\RedEdr\RedEdr.exe" +#$targetPath = "D:\toolz\procexp64.exe" +$targetPath = "D:\hacking\some_malware\mimikatz.exe" +#$targetPath = "D:\hacking\malware\cs2025-stageless.exe" +$webserverUrl = "http://localhost:8081" + +# Start RedEdr in the background +#Write-Host "Starting RedEdr..." +#$rededrProcess = Start-Process -FilePath $rededrPath -PassThru + +# Wait for the webserver to be ready +#Write-Host "Waiting for webserver to start..." +#Start-Sleep -Seconds 3 + +# Call /api/trace/start to start tracing +# Extract filename without path from targetPath +$targetFilename = [System.IO.Path]::GetFileName($targetPath) +Write-Host "Starting trace for $targetFilename..." +$traceBody = @{ + trace = @($targetFilename) +} | ConvertTo-Json + +try { + Invoke-RestMethod -Uri "$webserverUrl/api/trace/start" -Method Post -Body $traceBody -ContentType "application/json" + Write-Host "Trace started successfully" +} catch { + Write-Host "Failed to start trace: $_" +} + +# Start target executable and wait for it to exit +Write-Host "Starting $targetFilename..." +$procexpProcess = Start-Process -FilePath $targetPath -PassThru +Write-Host "$targetFilename PID: $($procexpProcess.Id) (0x$($procexpProcess.Id.ToString('X')))" +$procexpProcess | Wait-Process + +# Call /api/trace/reset to reset the trace +Write-Host "Resetting trace..." +try { + Invoke-RestMethod -Uri "$webserverUrl/api/trace/stop" -Method Post + Write-Host "Trace reset successfully" +} catch { + Write-Host "Failed to reset trace: $_" +} + +# Kill RedEdr +#Write-Host "Stopping RedEdr..." +#if ($rededrProcess -and !$rededrProcess.HasExited) { +# Stop-Process -Id $rededrProcess.Id -Force +# Write-Host "RedEdr stopped" +#} else { +# Write-Host "RedEdr process already exited" +#} + diff --git a/rededrtest.ps1 b/rededrtest.ps1 deleted file mode 100644 index ecba30f..0000000 --- a/rededrtest.ps1 +++ /dev/null @@ -1,52 +0,0 @@ -param( - [string]$arg = $args[0] # Default to the first argument if not provided -) - -if ($arg -eq "dll") { - # Start notepad.exe in the background - Start-Process notepad - Start-Sleep -Seconds 1 - - $notepadProcess = Get-Process notepad - $notepadProcessPid = $notepadProcess.Id - - Start-Process cmd -ArgumentList @( - "/c", - "timeout /t 1 &" - ".\x64\Debug\RedEdrTester.exe 3 $($notepadProcessPid) &" - #"timeout /t 3 &", # Wait for 5 seconds - #"taskkill /im notepad.exe /f" # Kill notepad.exe - ) - Start-Process -Wait "C:\rededr\rededr.exe" -ArgumentList "--web --hide --dllreader --trace otepad" - Stop-Process -Name notepad -Force -ErrorAction SilentlyContinue -} -elseif ($arg -eq "kernel") { - Start-Process cmd -ArgumentList @( - "/c", - "timeout /t 2 &", - "start notepad.exe &", - "timeout /t 3 &", - "taskkill /im notepad.exe /f" - ) - Start-Process -Wait "C:\rededr\rededr.exe" -ArgumentList "--hide --kernel --inject --trace otepad" -} -elseif ($arg -eq "etw") { - Start-Process cmd -ArgumentList @( - "/c", - "timeout /t 2 &", - "start notepad.exe &", - "timeout /t 3 &", - "taskkill /im notepad.exe /f" - ) - Start-Process -Wait "C:\rededr\rededr.exe" -ArgumentList "--etw --trace otepad" -} -elseif ($arg -eq "etwti") { - Start-Process cmd -ArgumentList @( - "/c", - "timeout /t 2 &", - 'start C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe &', - "timeout /t 3 &", - "taskkill /im msedge.exe /f" - ) - Start-Process -Wait "C:\rededr\rededr.exe" -ArgumentList "--etwti --trace otepad" -} \ No newline at end of file