From 33202c4c8fa552b8b6ee3d4d14e6f826a55f70bb Mon Sep 17 00:00:00 2001 From: es3n1n Date: Tue, 13 May 2025 17:10:36 +0900 Subject: [PATCH 1/2] fix(inject): set read_image_file_exec_options to 0 --- cxx-shared/cxx-shared.vcxitems | 1 + cxx-shared/shared/native.hpp | 31 +++++++++++++++++++++++++++++++ defendnot-loader/core/inject.cpp | 5 +++++ 3 files changed, 37 insertions(+) create mode 100644 cxx-shared/shared/native.hpp diff --git a/cxx-shared/cxx-shared.vcxitems b/cxx-shared/cxx-shared.vcxitems index 081badd..176a9e5 100644 --- a/cxx-shared/cxx-shared.vcxitems +++ b/cxx-shared/cxx-shared.vcxitems @@ -18,6 +18,7 @@ + \ No newline at end of file diff --git a/cxx-shared/shared/native.hpp b/cxx-shared/shared/native.hpp new file mode 100644 index 0000000..fe880a9 --- /dev/null +++ b/cxx-shared/shared/native.hpp @@ -0,0 +1,31 @@ +#pragma once +#include +#include + +#pragma pack(push, 1) +namespace native { + class PEB { + public: + std::uint8_t inherited_address_space; + std::uint8_t read_image_file_exec_options; + /// - we don't need other fields + }; + + static_assert(offsetof(PEB, read_image_file_exec_options) == 1); + + inline PEB* get_peb() { + static auto function = reinterpret_cast(GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlGetCurrentPeb")); + + if (function == nullptr) { + throw std::runtime_error("no RtlGetCurrentPeb"); + } + + static auto result = function(); + if (result == nullptr) [[unlikely]] { + throw std::runtime_error("no peb"); + } + + return result; + } +} // namespace native +#pragma pack(pop) diff --git a/defendnot-loader/core/inject.cpp b/defendnot-loader/core/inject.cpp index 6cf26fe..0f2c2db 100644 --- a/defendnot-loader/core/inject.cpp +++ b/defendnot-loader/core/inject.cpp @@ -1,6 +1,8 @@ #include "core/core.hpp" #include "shared/defer.hpp" +#include "shared/native.hpp" + #include #include @@ -19,6 +21,9 @@ namespace loader { .bInheritHandle = TRUE, }; + /// \xref: https://github.com/es3n1n/defendnot/issues/7#issuecomment-2874903650 + native::get_peb()->read_image_file_exec_options = 0; + std::println("** booting {}", proc_name); if (!CreateProcessA(nullptr, const_cast(proc_name.data()), &sa, &sa, FALSE, CREATE_SUSPENDED, nullptr, nullptr, &si, &pi)) { throw std::runtime_error(std::format("unable to create process: {}", GetLastError())); From 73a3633b4eb0f26e3ccc0b2627989f82c19dbef0 Mon Sep 17 00:00:00 2001 From: es3n1n Date: Tue, 13 May 2025 18:38:25 +0900 Subject: [PATCH 2/2] fix(inject): add debug flags to process --- cxx-shared/shared/native.hpp | 30 +++++++++++++++++++++++++----- defendnot-loader/core/inject.cpp | 8 +++++++- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/cxx-shared/shared/native.hpp b/cxx-shared/shared/native.hpp index fe880a9..e7235b9 100644 --- a/cxx-shared/shared/native.hpp +++ b/cxx-shared/shared/native.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include @@ -13,13 +14,22 @@ namespace native { static_assert(offsetof(PEB, read_image_file_exec_options) == 1); - inline PEB* get_peb() { - static auto function = reinterpret_cast(GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlGetCurrentPeb")); - - if (function == nullptr) { - throw std::runtime_error("no RtlGetCurrentPeb"); + template + inline Ty get_system_routine(const std::string_view module_name, const std::string_view function_name) { + const auto mod = GetModuleHandleA(module_name.data()); + if (mod == nullptr) { + throw std::runtime_error(std::format("unable to find module {}", module_name)); } + auto function = reinterpret_cast(GetProcAddress(mod, function_name.data())); + if (function == nullptr) { + throw std::runtime_error(std::format("unable to obtain {} from {}", module_name, function_name)); + } + return function; + } + + inline PEB* get_peb() { + static auto function = get_system_routine("ntdll.dll", "RtlGetCurrentPeb"); static auto result = function(); if (result == nullptr) [[unlikely]] { throw std::runtime_error("no peb"); @@ -27,5 +37,15 @@ namespace native { return result; } + + inline bool debug_set_process_kill_on_exit(const bool value) { + static auto function = get_system_routine("kernel32.dll", "DebugSetProcessKillOnExit"); + return static_cast(function(static_cast(value))); + } + + inline bool debug_active_process_stop(const std::uint32_t process_id) { + static auto function = get_system_routine("kernel32.dll", "DebugActiveProcessStop"); + return static_cast(function(process_id)); + } } // namespace native #pragma pack(pop) diff --git a/defendnot-loader/core/inject.cpp b/defendnot-loader/core/inject.cpp index 0f2c2db..4834c97 100644 --- a/defendnot-loader/core/inject.cpp +++ b/defendnot-loader/core/inject.cpp @@ -21,14 +21,20 @@ namespace loader { .bInheritHandle = TRUE, }; + /// By setting ReadImageFileExecOptions to FALSE and attaching ourselves as a debugger we can skip the IFEO /// \xref: https://github.com/es3n1n/defendnot/issues/7#issuecomment-2874903650 native::get_peb()->read_image_file_exec_options = 0; std::println("** booting {}", proc_name); - if (!CreateProcessA(nullptr, const_cast(proc_name.data()), &sa, &sa, FALSE, CREATE_SUSPENDED, nullptr, nullptr, &si, &pi)) { + const auto process_flags = CREATE_SUSPENDED | DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS; + if (!CreateProcessA(nullptr, const_cast(proc_name.data()), &sa, &sa, FALSE, process_flags, nullptr, nullptr, &si, &pi)) { throw std::runtime_error(std::format("unable to create process: {}", GetLastError())); } + /// Detach + native::debug_set_process_kill_on_exit(false); + native::debug_active_process_stop(pi.dwProcessId); + defer->void { CloseHandle(pi.hThread); /// Not closing hProcess because we return it