diff --git a/Debugger.vcxproj b/Debugger.vcxproj index 120d7c3..143d585 100644 --- a/Debugger.vcxproj +++ b/Debugger.vcxproj @@ -74,7 +74,7 @@ true false stdcpp20 - false + true true true @@ -89,8 +89,8 @@ true .\Debug\Syringe.pdb Windows - false - + true + true MachineX86 true true @@ -121,7 +121,7 @@ /Zc:threadSafeInit- /Zc:throwingNew /Gw %(AdditionalOptions) false stdcpp20 - false + true true $(ProjectDir)external\include;%(AdditionalIncludeDirectories) @@ -133,8 +133,8 @@ .\Release\Syringe.exe .\Release\Syringe.pdb Windows - false - + true + true MachineX86 true true diff --git a/Handle.h b/Handle.h index d98fed5..ca476e6 100644 --- a/Handle.h +++ b/Handle.h @@ -176,7 +176,7 @@ struct VirtualMemoryHandle { if (process && size) { - this->Value = VirtualAllocEx(process, address, size, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); + this->Value = VirtualAllocEx(process, address, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); } } @@ -217,6 +217,19 @@ struct VirtualMemoryHandle return static_cast(this->Value); } + bool protect(SIZE_T size, DWORD protection) const noexcept + { + DWORD oldProtection; + return this->Value && this->Process + && VirtualProtectEx(this->Process, this->Value, size, protection, &oldProtection) != FALSE; + } + + bool flush_instruction_cache(SIZE_T size) const noexcept + { + return this->Value && this->Process + && FlushInstructionCache(this->Process, this->Value, size) != FALSE; + } + void clear() noexcept { VirtualMemoryHandle(std::move(*this)); diff --git a/Syringe.sln b/Syringe.sln index 9fea6de..6904527 100644 --- a/Syringe.sln +++ b/Syringe.sln @@ -29,12 +29,12 @@ Global {DC2E7848-31D1-43EA-90D5-A5F1FB28E8AC}.Release|Mixed Platforms.Build.0 = Release|Win32 {DC2E7848-31D1-43EA-90D5-A5F1FB28E8AC}.Release|Win32.ActiveCfg = Release|Win32 {DC2E7848-31D1-43EA-90D5-A5F1FB28E8AC}.Release|Win32.Build.0 = Release|Win32 - {A1B2C3D4-1234-5678-9ABC-DEF012345678}.Debug|Any CPU.ActiveCfg = Release|Win32 - {A1B2C3D4-1234-5678-9ABC-DEF012345678}.Debug|Any CPU.Build.0 = Release|Win32 - {A1B2C3D4-1234-5678-9ABC-DEF012345678}.Debug|Mixed Platforms.ActiveCfg = Release|Win32 - {A1B2C3D4-1234-5678-9ABC-DEF012345678}.Debug|Mixed Platforms.Build.0 = Release|Win32 - {A1B2C3D4-1234-5678-9ABC-DEF012345678}.Debug|Win32.ActiveCfg = Release|Win32 - {A1B2C3D4-1234-5678-9ABC-DEF012345678}.Debug|Win32.Build.0 = Release|Win32 + {A1B2C3D4-1234-5678-9ABC-DEF012345678}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {A1B2C3D4-1234-5678-9ABC-DEF012345678}.Debug|Any CPU.Build.0 = Debug|Win32 + {A1B2C3D4-1234-5678-9ABC-DEF012345678}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {A1B2C3D4-1234-5678-9ABC-DEF012345678}.Debug|Mixed Platforms.Build.0 = Debug|Win32 + {A1B2C3D4-1234-5678-9ABC-DEF012345678}.Debug|Win32.ActiveCfg = Debug|Win32 + {A1B2C3D4-1234-5678-9ABC-DEF012345678}.Debug|Win32.Build.0 = Debug|Win32 {A1B2C3D4-1234-5678-9ABC-DEF012345678}.Release|Any CPU.ActiveCfg = Release|Win32 {A1B2C3D4-1234-5678-9ABC-DEF012345678}.Release|Mixed Platforms.ActiveCfg = Release|Win32 {A1B2C3D4-1234-5678-9ABC-DEF012345678}.Release|Mixed Platforms.Build.0 = Release|Win32 diff --git a/SyringeDebugger.cpp b/SyringeDebugger.cpp index 72d70bc..7348cbb 100644 --- a/SyringeDebugger.cpp +++ b/SyringeDebugger.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include @@ -19,7 +20,8 @@ using namespace std; void SyringeDebugger::DebugProcess(std::string_view const arguments) { - STARTUPINFO startupInfo{ sizeof(startupInfo) }; + STARTUPINFO startupInfo{}; + startupInfo.cb = sizeof(startupInfo); SetEnvironmentVariable("_NO_DEBUG_HEAP", "1"); @@ -42,6 +44,12 @@ bool SyringeDebugger::PatchMem(void* address, void const* buffer, DWORD size) return (WriteProcessMemory(workingHandle, address, buffer, size, nullptr) != FALSE); } +bool SyringeDebugger::PatchCode(void* address, void const* buffer, DWORD size) +{ + return PatchMem(address, buffer, size) + && FlushInstructionCache(workingHandle, address, size) != FALSE; +} + bool SyringeDebugger::ReadMem(void const* address, void* buffer, DWORD size) { return (ReadProcessMemory(workingHandle, address, buffer, size, nullptr) != FALSE); @@ -57,6 +65,15 @@ VirtualMemoryHandle SyringeDebugger::AllocMem(void* address, size_t size) throw_lasterror_or(ERROR_ERRORS_ENCOUNTERED, exe); } +void SyringeDebugger::MakeExecutable(VirtualMemoryHandle const& memory, size_t size) +{ + if (!memory.protect(size, PAGE_EXECUTE_READ) + || !memory.flush_instruction_cache(size)) + { + throw_lasterror_or(ERROR_ERRORS_ENCOUNTERED, exe); + } +} + bool SyringeDebugger::SetBP(void* address) { // save overwritten code and set INT 3 @@ -64,7 +81,7 @@ bool SyringeDebugger::SetBP(void* address) { auto const buffer = INT3; ReadMem(address, &opcode, 1); - return PatchMem(address, &buffer, 1); + return PatchCode(address, &buffer, 1); } return true; @@ -352,14 +369,20 @@ DWORD SyringeDebugger::HandleException(DEBUG_EVENT const& dbgEvent) { auto const buffer = INT3; context.EFlags &= ~0x100; - PatchMem(threadInfo.lastBP, &buffer, 1); + if (!PatchCode(threadInfo.lastBP, &buffer, 1)) + { + throw_lasterror_or(ERROR_ERRORS_ENCOUNTERED, exe); + } } // load DLLs and retrieve proc addresses if (!bDLLsLoaded) { // restore - PatchMem(exceptAddr, &Breakpoints[exceptAddr].original_opcode, 1); + if (!PatchCode(exceptAddr, &Breakpoints[exceptAddr].original_opcode, 1)) + { + throw_lasterror_or(ERROR_ERRORS_ENCOUNTERED, exe); + } if (loop_LoadLibrary == v_AllHooks.end()) { @@ -387,7 +410,7 @@ DWORD SyringeDebugger::HandleException(DEBUG_EVENT const& dbgEvent) PatchMem(&GetData()->LibName, hook->lib, MaxNameLength); PatchMem(&GetData()->ProcName, hook->proc, MaxNameLength); - context.Eip = reinterpret_cast(&GetData()->LoadLibraryFunc); + context.Eip = reinterpret_cast(GetLoaderCode()); } else { @@ -402,7 +425,7 @@ DWORD SyringeDebugger::HandleException(DEBUG_EVENT const& dbgEvent) PatchMem(&GetData()->LibName, entry.lib, MaxNameLength); PatchMem(&GetData()->ProcName, entry.symbol, MaxNameLength); - context.Eip = reinterpret_cast(&GetData()->LoadLibraryFunc); + context.Eip = reinterpret_cast(GetLoaderCode()); } else { @@ -425,7 +448,10 @@ DWORD SyringeDebugger::HandleException(DEBUG_EVENT const& dbgEvent) if (!bFeaturesSet) { // restore - PatchMem(exceptAddr, &Breakpoints[exceptAddr].original_opcode, 1); + if (!PatchCode(exceptAddr, &Breakpoints[exceptAddr].original_opcode, 1)) + { + throw_lasterror_or(ERROR_ERRORS_ENCOUNTERED, exe); + } // read the resolved address of the feature flag in the target process void* flagAddr = nullptr; @@ -454,7 +480,7 @@ DWORD SyringeDebugger::HandleException(DEBUG_EVENT const& dbgEvent) PatchMem(&GetData()->LibName, entry.lib, MaxNameLength); PatchMem(&GetData()->ProcName, entry.symbol, MaxNameLength); - context.Eip = reinterpret_cast(&GetData()->LoadLibraryFunc); + context.Eip = reinterpret_cast(GetLoaderCode()); } else { @@ -610,7 +636,11 @@ DWORD SyringeDebugger::HandleException(DEBUG_EVENT const& dbgEvent) p_code += sizeof(jmp_back); auto const actual_sz = static_cast(p_code - code.data()); - PatchMem(base, code.data(), actual_sz); + if (!PatchMem(base, code.data(), actual_sz)) + { + throw_lasterror_or(ERROR_ERRORS_ENCOUNTERED, exe); + } + MakeExecutable(it.second.p_caller_code, sz); // dump /* @@ -637,7 +667,10 @@ DWORD SyringeDebugger::HandleException(DEBUG_EVENT const& dbgEvent) ApplyPatch(code.data(), jmp); ApplyPatch(code.data() + 0x01, rel2); - PatchMem(p_original_code, code.data(), code.size()); + if (!PatchCode(p_original_code, code.data(), static_cast(code.size()))) + { + throw_lasterror_or(ERROR_ERRORS_ENCOUNTERED, exe); + } } Log::Flush(); @@ -646,7 +679,10 @@ DWORD SyringeDebugger::HandleException(DEBUG_EVENT const& dbgEvent) } // restore - PatchMem(exceptAddr, &Breakpoints[exceptAddr].original_opcode, 1); + if (!PatchCode(exceptAddr, &Breakpoints[exceptAddr].original_opcode, 1)) + { + throw_lasterror_or(ERROR_ERRORS_ENCOUNTERED, exe); + } // single step mode context.EFlags |= 0x100; @@ -673,7 +709,10 @@ DWORD SyringeDebugger::HandleException(DEBUG_EVENT const& dbgEvent) { auto const buffer = INT3; auto const& threadInfo = Threads[dbgEvent.dwThreadId]; - PatchMem(threadInfo.lastBP, &buffer, 1); + if (!PatchCode(threadInfo.lastBP, &buffer, 1)) + { + throw_lasterror_or(ERROR_ERRORS_ENCOUNTERED, exe); + } HANDLE hThread = threadInfo.Thread; CONTEXT context; @@ -785,27 +824,19 @@ DWORD SyringeDebugger::HandleException(DEBUG_EVENT const& dbgEvent) return DBG_EXCEPTION_NOT_HANDLED; } - return DBG_CONTINUE; } void SyringeDebugger::Run(std::string_view const arguments) { - constexpr auto AllocDataSize = sizeof(AllocData); - Log::WriteLine( __FUNCTION__ ": Running process to debug. cmd = \"%s %.*s\"", exe.c_str(), printable(arguments)); DebugProcess(arguments); - Log::WriteLine(__FUNCTION__ ": Allocating 0x%u bytes...", AllocDataSize); - pAlloc = AllocMem(nullptr, AllocDataSize); - - Log::WriteLine(__FUNCTION__ ": pAlloc = 0x%08X", pAlloc.get()); - // write DLL loader code Log::WriteLine(__FUNCTION__ ": Writing DLL loader & caller code..."); - static BYTE const cLoadLibrary[] = { + static constexpr BYTE cLoadLibrary[] = { 0x50, // push eax 0x51, // push ecx 0x52, // push edx @@ -823,17 +854,22 @@ void SyringeDebugger::Run(std::string_view const arguments) INT3, NOP // int3 and some padding }; - std::array data; - static_assert(AllocData::CodeSize >= sizeof(cLoadLibrary)); - ApplyPatch(data.data(), cLoadLibrary); - ApplyPatch(data.data() + 0x04, &GetData()->LibName); - ApplyPatch(data.data() + 0x0A, pImLoadLibrary); - ApplyPatch(data.data() + 0x13, &GetData()->ProcName); - ApplyPatch(data.data() + 0x1A, pImGetProcAddress); - ApplyPatch(data.data() + 0x1F, &GetData()->ProcAddress); - PatchMem(pAlloc, data.data(), data.size()); + auto code = std::to_array(cLoadLibrary); + pLoaderCode = AllocMem(nullptr, code.size()); + pExchangeData = AllocMem(nullptr, sizeof(ExchangeData)); - Log::WriteLine(__FUNCTION__ ": pcLoadLibrary = 0x%08X", &GetData()->LoadLibraryFunc); + ApplyPatch(code.data() + 0x04, &GetData()->LibName); + ApplyPatch(code.data() + 0x0A, pImLoadLibrary); + ApplyPatch(code.data() + 0x13, &GetData()->ProcName); + ApplyPatch(code.data() + 0x1A, pImGetProcAddress); + ApplyPatch(code.data() + 0x1F, &GetData()->ProcAddress); + if (!PatchMem(pLoaderCode, code.data(), code.size())) + { + throw_lasterror_or(ERROR_ERRORS_ENCOUNTERED, exe); + } + MakeExecutable(pLoaderCode, code.size()); + + Log::WriteLine(__FUNCTION__ ": pcLoadLibrary = 0x%08X", GetLoaderCode()); // breakpoints for DLL loading and proc address retrieving bDLLsLoaded = false; @@ -842,7 +878,10 @@ void SyringeDebugger::Run(std::string_view const arguments) loop_LoadLibrary = v_AllHooks.end(); // set breakpoint - SetBP(pcEntryPoint); + if (!SetBP(pcEntryPoint)) + { + throw_lasterror_or(ERROR_ERRORS_ENCOUNTERED, exe); + } DEBUG_EVENT dbgEvent; ResumeThread(pInfo.hThread); @@ -945,7 +984,10 @@ void SyringeDebugger::RemoveBP(LPVOID const address, bool const restoreOpcode) { if (restoreOpcode) { - PatchMem(address, &i->second.original_opcode, 1); + if (!PatchCode(address, &i->second.original_opcode, 1)) + { + throw_lasterror_or(ERROR_ERRORS_ENCOUNTERED, exe); + } } Breakpoints.erase(i); diff --git a/SyringeDebugger.h b/SyringeDebugger.h index a56eb65..e7e603e 100644 --- a/SyringeDebugger.h +++ b/SyringeDebugger.h @@ -6,6 +6,7 @@ #include "PortableExecutable.h" #include "Log.h" +#include #include #include #include @@ -47,23 +48,25 @@ class SyringeDebugger std::string_view const flagView = flag; // parse all -i=filename_to_inject from flags - if (auto const pos = flagView.find(INCLUDE_FLAG); pos != std::string_view::npos) + if (auto const includePos = flagView.find(INCLUDE_FLAG); + includePos != std::string_view::npos) { - dlls.emplace_back(flagView.begin() + pos + INCLUDE_FLAG.size(), flagView.end()); + dlls.emplace_back( + flagView.begin() + includePos + INCLUDE_FLAG.size(), flagView.end()); } - else if (auto const pos = flagView.find(DETACH_FLAG); pos != std::string_view::npos) + else if (flagView.find(DETACH_FLAG) != std::string_view::npos) { bDetachWhenDone = true; } - else if (auto const pos = flagView.find(NODETACH_FLAG); pos != std::string_view::npos) + else if (flagView.find(NODETACH_FLAG) != std::string_view::npos) { bDetachWhenDone = false; } - else if (auto const pos = flagView.find(NOWAIT_FLAG); pos != std::string_view::npos) + else if (flagView.find(NOWAIT_FLAG) != std::string_view::npos) { bWaitForProcessEnd = false; } - else if (auto const pos = flagView.find(HANDSHAKES_FLAG); pos != std::string_view::npos) + else if (flagView.find(HANDSHAKES_FLAG) != std::string_view::npos) { bHandshakes = true; } @@ -91,7 +94,9 @@ class SyringeDebugger // memory VirtualMemoryHandle AllocMem(void* address, size_t size); + void MakeExecutable(VirtualMemoryHandle const& memory, size_t size); bool PatchMem(void* address, void const* buffer, DWORD size); + bool PatchCode(void* address, void const* buffer, DWORD size); bool ReadMem(void const* address, void* buffer, DWORD size); // syringe @@ -186,7 +191,8 @@ class SyringeDebugger void* pcEntryPoint{ nullptr }; void* pImLoadLibrary{ nullptr }; void* pImGetProcAddress{ nullptr }; - VirtualMemoryHandle pAlloc; + VirtualMemoryHandle pLoaderCode; + VirtualMemoryHandle pExchangeData; DWORD dwTimeStamp{ 0u }; DWORD dwExeSize{ 0u }; DWORD dwExeCRC{ 0u }; @@ -200,19 +206,21 @@ class SyringeDebugger bool bAVLogged{ false }; - // data addresses - struct AllocData + struct ExchangeData { - static constexpr auto CodeSize = 0x40u; - std::byte LoadLibraryFunc[CodeSize]; void* ProcAddress; char LibName[MaxNameLength]; char ProcName[MaxNameLength]; }; - AllocData* GetData() const noexcept + ExchangeData* GetData() const noexcept { - return reinterpret_cast(pAlloc.get()); + return reinterpret_cast(pExchangeData.get()); + }; + + BYTE* GetLoaderCode() const noexcept + { + return pLoaderCode.get(); }; struct HookBuffer diff --git a/Tests.vcxproj b/Tests.vcxproj index fd8f7e2..798aabd 100644 --- a/Tests.vcxproj +++ b/Tests.vcxproj @@ -1,6 +1,10 @@ + + Debug + Win32 + Release Win32 @@ -12,6 +16,12 @@ Tests + + Application + v143 + false + MultiByte + Application v143 @@ -22,15 +32,51 @@ + + + + + $(SolutionDir)tests\bin\Debug\ + $(SolutionDir)tests\obj\Debug\ + false + $(SolutionDir)tests\bin\ $(SolutionDir)tests\obj\ false + + + Disabled + WIN32;_DEBUG;_CONSOLE;NOMINMAX;ZYDIS_STATIC_BUILD;ZYCORE_STATIC_BUILD;SYRINGE_TESTING;%(PreprocessorDefinitions) + MultiThreadedDebug + Level4 + StreamingSIMDExtensions + /Zc:threadSafeInit- /Zc:throwingNew /Gw %(AdditionalOptions) + true + false + stdcpp20 + false + true + $(SolutionDir);$(SolutionDir)external\include;%(AdditionalIncludeDirectories) + $(IntDir)\%(RelativeDir) + $(IntDir)\%(RelativeDir) + + + $(OutDir)Tests.exe + Console + false + MachineX86 + false + false + kernel32.lib;user32.lib;gdi32.lib;advapi32.lib;shell32.lib;dbghelp.lib;%(AdditionalDependencies) + true + + Full