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;
+}