diff --git a/Debugger.vcxproj b/Debugger.vcxproj index 120d7c3..7c48406 100644 --- a/Debugger.vcxproj +++ b/Debugger.vcxproj @@ -148,15 +148,18 @@ + + + diff --git a/Debugger.vcxproj.filters b/Debugger.vcxproj.filters index c4cd35f..1039ce5 100644 --- a/Debugger.vcxproj.filters +++ b/Debugger.vcxproj.filters @@ -13,6 +13,9 @@ {e85625c5-48c3-4acf-a47a-873697970105} ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe + + {0a7cbdbd-6556-4c84-b288-4ffa95f8120b} + @@ -30,6 +33,12 @@ Source Files + + Components + + + Components + @@ -56,6 +65,9 @@ Header Files + + Components + diff --git a/HookAnalyzer.cpp b/HookAnalyzer.cpp new file mode 100644 index 0000000..c729d9e --- /dev/null +++ b/HookAnalyzer.cpp @@ -0,0 +1,147 @@ +#include "HookAnalyzer.h" +#include "Handle.h" +//#include "Setting.h" +#include +#include "Log.h" +#include "resource.h" + +const std::string& ExecutableDirectoryPath(); + +void HookAnalyzer::Add(HookAnalyzeData&& Data, bool Show) +{ + if (Show) + { + ByLibName[Data.Lib].push_back(Data); + HookMap[Data.Lib + AnalyzerDelim + Data.Proc] = Data; + ByAddress[Data.Addr].push_back(Data); + } + + HookMapEx[Data.Lib + AnalyzerDelim + Data.Proc] = Data; + ByLibNameEx[Data.Lib].push_back(Data); + ByAddressEx[Data.RelLib][Data.Addr].push_back(std::move(Data)); +} + +bool HookAnalyzer::ReportLOG(bool ByAddr, bool ByLib) +{ + constexpr auto const VersionString = "SyringeEx " SYRINGEEX_VER_TEXT ", based on Syringe 0.7.2.0"; + + FileHandle File = FileHandle(fopen("HookAnalysis.log", "w")); + if (!File)return false; + fprintf(File, "%s will analyze the specified hooks.\n", VersionString); + if (ByAddr) + { + fputs("========================\n", File); + fputs("By Hook Position: (Execution order for each address)\n", File); + for (auto& p : ByAddress) + { + fprintf(File, "At %08X : \n", p.first); + for (auto v : p.second) + { + //fprintf(File, "Hook\"%s, Relative to\"%s\", From\"%s\", %d Bytes Overridden ,Priority %d, Sub Priority \"%s\"\n", v.Proc.c_str(), v.RelLib.c_str(), v.Lib.c_str(), v.Len, v.Priority, v.SubPriority.c_str()); + fprintf(File, "Hook\"%s, From\"%s\", %d Bytes Overridden\n", v.Proc.c_str(), v.Lib.c_str(), v.Len); + } + } + } + + if (ByLib) + { + fputs("========================\n", File); + fputs("By Hook Source: \n", File); + for (auto& p : ByLibName) + { + fprintf(File, "Analyzing DLL : \"%s\" ……\n", p.first.c_str()); + for (auto v : p.second) + { + //fprintf(File, "Hook\"%s, Relative to\"%s\", From\"%s\", %d Bytes Overridden ,Priority %d, Sub Priority \"%s\"\n", v.Proc.c_str(), v.RelLib.c_str(), v.Lib.c_str(), v.Len, v.Priority, v.SubPriority.c_str()); + fprintf(File, "Hook\"%s, From\"%s\", %d Bytes Overridden\n", v.Proc.c_str(), v.Lib.c_str(), v.Len); + } + } + } + + fputs("========================\n", File); + fprintf(File, "%s : Complete.\n", VersionString); + return true; +} + +bool HookAnalyzer::ReportNDJSON() +{ + //TODO + //Until a JSON library is chosen and imported + //A format may work : + //every line : + //Hook Address / Name / Source / Bytes Overridden +} + +bool HookAnalyzer::HasHookConflict(bool ShowHookConflictPopup) +{ + //check if there are conflicting hooks + bool Conflict = false; + for (auto& [lib, byaddr] : ByAddressEx) + { + std::vector*> SortedHooks; + for (auto& p : byaddr) + SortedHooks.push_back(&p.second); + std::sort(SortedHooks.begin(), SortedHooks.end(), [](const auto& lhs, const auto& rhs) -> bool + { + return lhs->front().Addr < rhs->front().Addr; + }); + for (size_t i = 0; i < SortedHooks.size() - 1; i++) + { + auto Addr1 = SortedHooks[i]->front().Addr; + auto Addr2 = SortedHooks[i + 1]->front().Addr; + auto Len1 = std::max_element(SortedHooks[i]->begin(), SortedHooks[i]->end(), [](const auto& lhs, const auto& rhs) -> bool + { + return lhs.Len < rhs.Len; + })->Len; + Len1 = std::max(Len1, 5);//a JMP is 5 bytes + if (Addr1 + Len1 > Addr2) + { + Log::WriteLine("Hook Conflict Detected:"); + for (auto& v : *SortedHooks[i]) + //Log::WriteLine("Hook\"%s, Relative to\"%s\", From\"%s\", %d Bytes Overridden ,Priority %d, Sub Priority \"%s\"\n", v.Proc.c_str(), v.RelLib.c_str(), v.Lib.c_str(), v.Len, v.Priority, v.SubPriority.c_str()); + Log::WriteLine("Hook\"%s, From\"%s\", %d Bytes Overridden\n", v.Proc.c_str(), v.Lib.c_str(), v.Len); + for (auto& v : *SortedHooks[i + 1]) + //Log::WriteLine("Hook\"%s, Relative to\"%s\", From\"%s\", %d Bytes Overridden ,Priority %d, Sub Priority \"%s\"\n", v.Proc.c_str(), v.RelLib.c_str(), v.Lib.c_str(), v.Len, v.Priority, v.SubPriority.c_str()); + Log::WriteLine("Hook\"%s, From\"%s\", %d Bytes Overridden\n", v.Proc.c_str(), v.Lib.c_str(), v.Len); + if (!Conflict && ShowHookConflictPopup) + { + wchar_t ErrorStr[1000]; + swprintf_s(ErrorStr, 1000, L"Hook Conflict detected at 0x%08X and 0x%08X , see details in Syringe.log.", Addr1, Addr2); + MessageBoxW(NULL, ErrorStr, L"SyringeEx", MB_OK | MB_ICONERROR); + } + Conflict = true; + } + } + } + return Conflict; +} + +bool HookAnalyzer::GenerateINJ() +{ + //Log::WriteLine(ExecutableDirectoryPath().c_str()); + auto path = ExecutableDirectoryPath() + "\\INJ"; + auto pp = CreateDirectoryA(path.c_str(), NULL); + if (pp || GetLastError() == ERROR_ALREADY_EXISTS) + { + //Log::WriteLine((path + "\\").c_str()); + for (auto& p : ByLibNameEx) + { + //Log::WriteLine((path + "\\" + p.first).c_str()); + FileHandle File = FileHandle(fopen((path + "\\" + p.first + ".inj").c_str(), "w")); + if (!File)return false; + for (auto& h : p.second) + { + if (!h.RelLib.empty()) + fputs(";Relative Hook Found ,failed to Generate", File); + else if (!h.SubPriority.empty()) + fprintf(File, "%X=%s,%X,%d,%s\n", h.Addr, h.Proc.c_str(), h.Len, h.Priority, h.SubPriority.c_str()); + else if (h.Priority == DefaultPriority) + fprintf(File, "%X=%s,%X\n", h.Addr, h.Proc.c_str(), h.Len); + else + fprintf(File, "%X=%s,%X,%d\n", h.Addr, h.Proc.c_str(), h.Len, h.Priority); + } + } + return true; + } + return false; +} \ No newline at end of file diff --git a/HookAnalyzer.h b/HookAnalyzer.h new file mode 100644 index 0000000..bfb7c6b --- /dev/null +++ b/HookAnalyzer.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include + +const std::string AnalyzerDelim = "\\*^*\\"; +const int DefaultPriority = 100000; + +struct HookAnalyzeData +{ + std::string Lib; + std::string Proc; + int Addr; + int Len; + + int Priority{ DefaultPriority }; + std::string SubPriority{ "" }; + std::string RelLib{ "" }; +}; + +class HookAnalyzer +{ +private: + std::unordered_map> ByLibName; + std::unordered_map> ByLibNameEx; +public: + std::unordered_map HookMap; + std::unordered_map> ByAddress; + std::unordered_map HookMapEx; + std::unordered_map>> ByAddressEx; + + void Add(HookAnalyzeData&& , bool Show); + bool ReportLOG(bool ByAddr, bool ByLib); + bool ReportNDJSON();//TODO + bool GenerateINJ(); + bool HasHookConflict(bool ShowHookConflictPopup); +}; + +//static constexpr size_t MaxNameLength = 0x100u; +// +//struct Hook +//{ +// char lib[MaxNameLength]; +// char proc[MaxNameLength]; +// void* proc_address; +// +// size_t num_overridden; +// int Priority; +// char SubPriority[MaxNameLength]; +// char RelativeLib[MaxNameLength]; +//}; \ No newline at end of file diff --git a/SyringeDebugger.cpp b/SyringeDebugger.cpp index 5715a6f..8dd6848 100644 --- a/SyringeDebugger.cpp +++ b/SyringeDebugger.cpp @@ -1,4 +1,4 @@ -#include "SyringeDebugger.h" +#include "SyringeDebugger.h" #include "CRC32.h" #include "FindFile.h" @@ -788,8 +788,28 @@ DWORD SyringeDebugger::HandleException(DEBUG_EVENT const& dbgEvent) return DBG_CONTINUE; } -void SyringeDebugger::Run(std::string_view const arguments) +void SyringeDebugger::Run(std::string_view arguments) { + if(bDryRun) + { + Log::WriteLine(__FUNCTION__ ": Entering dry run mode, not actually running the process."); + DryRun(arguments); + } + else + { + RealRun(arguments); + } +} + +void SyringeDebugger::DryRun(std::string_view const arguments) +{ + PreLoadData(); +} + +void SyringeDebugger::RealRun(std::string_view const arguments) +{ + PreLoadData(); + constexpr auto AllocDataSize = sizeof(AllocData); Log::WriteLine( @@ -937,6 +957,36 @@ void SyringeDebugger::Run(std::string_view const arguments) Log::WriteLine(); } +void SyringeDebugger::PreLoadData() +{ + if (bReportLOG) + { + Log::WriteLine(__FUNCTION__ ": Writing Hook Analysis Report LOG……", v_AllHooks.size()); + if(analyzer.ReportLOG(bReportLogByAddress, bReportLogByLibrary))Log::WriteLine(__FUNCTION__ ": Complete, see HookAnalysis.log 。", v_AllHooks.size()); + else Log::WriteLine(__FUNCTION__ ": Failed to generate.", v_AllHooks.size()); + } + + if (bReportJSON) + { + Log::WriteLine(__FUNCTION__ ": Writing Hook Analysis Report JSON……", v_AllHooks.size()); + if (analyzer.ReportNDJSON())Log::WriteLine(__FUNCTION__ ": Complete, see HookAnalysis.json 。", v_AllHooks.size()); + else Log::WriteLine(__FUNCTION__ ": Failed to generate.", v_AllHooks.size()); + } + + if (bDetectConflict) + { + analyzer.HasHookConflict(bShowHookConflictPopup); + } + + if (bGenerateINJ) + { + Log::WriteLine(__FUNCTION__ ": Creating INJ files..."); + if (analyzer.GenerateINJ()) + Log::WriteLine(__FUNCTION__ ": Complete."); + else Log::WriteLine(__FUNCTION__ ": Failed."); + } +} + void SyringeDebugger::RemoveBP(LPVOID const address, bool const restoreOpcode) { if (auto const i = Breakpoints.find(address); i != Breakpoints.end()) @@ -1108,6 +1158,11 @@ void SyringeDebugger::FindDLLs() { for (auto& i : it.second.hooks) { + std::string_view filename = i.lib; + auto sz = filename.find_last_of('\\'); + auto sv = (sz != std::string_view::npos) ? filename.substr(sz + 1, filename.size() - sz - 1) : filename; + analyzer.Add(HookAnalyzeData{ sv.data(), i.proc, (int)it.first, (int)i.num_overridden }, true); + v_AllHooks.push_back(&i); } } diff --git a/SyringeDebugger.h b/SyringeDebugger.h index 88797c2..0619940 100644 --- a/SyringeDebugger.h +++ b/SyringeDebugger.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #define WIN32_LEAN_AND_MEAN // WIN32_FAT_AND_STUPID @@ -22,6 +22,8 @@ #include #pragma warning(pop) +#include "HookAnalyzer.h" + using std::operator""sv; class SyringeDebugger @@ -36,6 +38,17 @@ class SyringeDebugger static constexpr std::string_view NODETACH_FLAG = "--nodetach"; static constexpr std::string_view NOWAIT_FLAG = "--nowait"; static constexpr std::string_view HANDSHAKES_FLAG = "--handshakes"; + static constexpr std::string_view DRYRUN_FLAG = "--dryrun"; + static constexpr std::string_view GENERATEINJ_FLAG = "--generate-inj"; + static constexpr std::string_view REPORT_LOG_FLAG = "--report-log"; + static constexpr std::string_view REPORT_JSON_FLAG = "--report-json"; + static constexpr std::string_view DETECT_CONFLICT_FLAG = "--detect-conflict"; + static constexpr std::string_view SHOW_HOOK_CONFLICT_POPUP_FLAG = "--show-hook-conflict-popup"; + static constexpr std::string_view NO_BY_ADDRESS_FLAG = "--no-by-address"; + static constexpr std::string_view NO_BY_LIBRARY_FLAG = "--no-by-library"; + + + public: SyringeDebugger(std::string_view filename, std::vector flags = {}) @@ -45,6 +58,10 @@ class SyringeDebugger { std::string_view const flagView = flag; +//Disable Warning C4456 +#pragma warning( push ) +#pragma warning( disable : 4456 ) + // parse all -i=filename_to_inject from flags if (auto const pos = flagView.find(INCLUDE_FLAG); pos != std::string_view::npos) { @@ -62,12 +79,46 @@ class SyringeDebugger { bHandshakes = true; } + else if (auto const pos = flagView.find(DRYRUN_FLAG); pos != std::string_view::npos) + { + bDryRun = true; + } + else if (auto const pos = flagView.find(GENERATEINJ_FLAG); pos != std::string_view::npos) + { + bGenerateINJ = true; + } + if (auto const pos = flagView.find(REPORT_LOG_FLAG); pos != std::string_view::npos) + { + bReportLOG = true; + } + else if (auto const pos = flagView.find(REPORT_JSON_FLAG); pos != std::string_view::npos) + { + bReportJSON = true; + } + else if (auto const pos = flagView.find(DETECT_CONFLICT_FLAG); pos != std::string_view::npos) + { + bDetectConflict = true; + } + else if (auto const pos = flagView.find(SHOW_HOOK_CONFLICT_POPUP_FLAG); pos != std::string_view::npos) + { + bShowHookConflictPopup = true; + } + else if (auto const pos = flagView.find(NO_BY_ADDRESS_FLAG); pos != std::string_view::npos) + { + bReportLogByAddress = false; + } + else if (auto const pos = flagView.find(NO_BY_LIBRARY_FLAG); pos != std::string_view::npos) + { + bReportLogByLibrary = false; + } else { Log::WriteLine(__FUNCTION__ ": Unknown flag \"%.*s\", skipping.", printable(flagView)); } } +#pragma warning( pop ) + if (dlls.empty()) { dlls.emplace_back("*.dll"); @@ -77,6 +128,8 @@ class SyringeDebugger } // debugger + void DryRun(std::string_view arguments); + void RealRun(std::string_view arguments); void Run(std::string_view arguments); DWORD HandleException(DEBUG_EVENT const& dbgEvent); @@ -189,12 +242,24 @@ class SyringeDebugger bool bDetachWhenDone{ true }; bool bWaitForProcessEnd{ true }; bool bHandshakes{ false }; + bool bDryRun{ false }; + bool bGenerateINJ{ false }; + bool bReportLOG{ false }; + bool bReportJSON{ false }; + bool bDetectConflict{ false }; + bool bShowHookConflictPopup{ false }; + bool bReportLogByAddress{ true }; + bool bReportLogByLibrary{ true }; + bool bDLLsLoaded{ false }; bool bHooksCreated{ false }; bool bAVLogged{ false }; + // components + HookAnalyzer analyzer; + // data addresses struct AllocData { @@ -244,6 +309,7 @@ class SyringeDebugger bool CanHostDLL(PortableExecutable const& DLL, IMAGE_SECTION_HEADER const& hosts) const; bool ParseHooksSection(PortableExecutable const& DLL, IMAGE_SECTION_HEADER const& hooks, HookBuffer& buffer); std::optional Handshake(char const* lib, int hooks, unsigned int crc); + void PreLoadData(); }; // disable "structures padded due to alignment specifier" diff --git a/ToolFunctions.cpp b/ToolFunctions.cpp new file mode 100644 index 0000000..47d84d6 --- /dev/null +++ b/ToolFunctions.cpp @@ -0,0 +1,77 @@ +#include +#include +#include + +const std::string& ExecutableDirectoryPath() +{ + static std::string ss; + if (!ss.empty())return ss; + std::vector full_path_exe(MAX_PATH); + + for (;;) + { + const DWORD result = GetModuleFileNameA(NULL, + &full_path_exe[0], + full_path_exe.size()); + + if (result == 0) + { + // Report failure to caller. + } + else if (full_path_exe.size() == result) + { + // Buffer too small: increase size. + full_path_exe.resize(full_path_exe.size() * 2); + } + else + { + // Success. + break; + } + } + + // Remove executable name. + std::string result(full_path_exe.begin(), full_path_exe.end()); + std::string::size_type i = result.find_last_of("\\/"); + if (std::string::npos != i) result.erase(i); + + ss = result; + return ss; +} + +const std::wstring& ExecutableDirectoryPathW() +{ + static std::wstring ss; + if (!ss.empty())return ss; + std::vector full_path_exe(MAX_PATH); + + for (;;) + { + const DWORD result = GetModuleFileNameW(NULL, + &full_path_exe[0], + full_path_exe.size()); + + if (result == 0) + { + // Report failure to caller. + } + else if (full_path_exe.size() == result) + { + // Buffer too small: increase size. + full_path_exe.resize(full_path_exe.size() * 2); + } + else + { + // Success. + break; + } + } + + // Remove executable name. + std::wstring result(full_path_exe.begin(), full_path_exe.end()); + std::wstring::size_type i = result.find_last_of(L"\\/"); + if (std::string::npos != i) result.erase(i); + + ss = result; + return ss; +}