Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Debugger.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -148,15 +148,18 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="CRC32.cpp" />
<ClCompile Include="HookAnalyzer.cpp" />
<ClCompile Include="Log.cpp" />
<ClCompile Include="Main.cpp" />
<ClCompile Include="PortableExecutable.cpp" />
<ClCompile Include="SyringeDebugger.cpp" />
<ClCompile Include="ToolFunctions.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="CRC32.h" />
<ClInclude Include="FindFile.h" />
<ClInclude Include="Handle.h" />
<ClInclude Include="HookAnalyzer.h" />
<ClInclude Include="Log.h" />
<ClInclude Include="PortableExecutable.h" />
<ClInclude Include="resource.h" />
Expand Down
12 changes: 12 additions & 0 deletions Debugger.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
<UniqueIdentifier>{e85625c5-48c3-4acf-a47a-873697970105}</UniqueIdentifier>
<Extensions>ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe</Extensions>
</Filter>
<Filter Include="Components">
<UniqueIdentifier>{0a7cbdbd-6556-4c84-b288-4ffa95f8120b}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="CRC32.cpp">
Expand All @@ -30,6 +33,12 @@
<ClCompile Include="SyringeDebugger.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="HookAnalyzer.cpp">
<Filter>Components</Filter>
</ClCompile>
<ClCompile Include="ToolFunctions.cpp">
<Filter>Components</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="CRC32.h">
Expand All @@ -56,6 +65,9 @@
<ClInclude Include="Support.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="HookAnalyzer.h">
<Filter>Components</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="res.rc">
Expand Down
144 changes: 144 additions & 0 deletions HookAnalyzer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#include "HookAnalyzer.h"
#include "Handle.h"
//#include "Setting.h"
#include <filesystem>
#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)
{
Log::WriteLine(__FUNCTION__ ": Hook Analysis ");
if (ByAddr)
{
Log::WriteLine("========================");
Log::WriteLine("By Hook Position: (Execution order for each address)");
for (auto& p : ByAddress)
{
Log::WriteLine("At %08X : ", p.first);
for (auto v : p.second)
{
//Log::WriteLine("Hook\"%s, Relative to\"%s\", From\"%s\", %d Bytes Overridden ,Priority %d, Sub Priority \"%s\"", 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", v.Proc.c_str(), v.Lib.c_str(), v.Len);
}
}
}

if (ByLib)
{
Log::WriteLine("========================");
Log::WriteLine("By Hook Source: ");
for (auto& p : ByLibName)
{
Log::WriteLine("Analyzing DLL : \"%s\" ……", p.first.c_str());
for (auto v : p.second)
{
//Log::WriteLine("Hook\"%s, Relative to\"%s\", At 0x%08X, From\"%s\", %d Bytes Overridden ,Priority %d, Sub Priority \"%s\"", v.Proc.c_str(), v.RelLib.c_str(), v.Addr, v.Lib.c_str(), v.Len, v.Priority, v.SubPriority.c_str());
Log::WriteLine("Hook\"%s, At 0x%08X, From\"%s\", %d Bytes Overridden", v.Proc.c_str(), v.Addr, v.Lib.c_str(), v.Len);
}
}
}

Log::WriteLine("========================");
Log::WriteLine(__FUNCTION__ ": Complete. ");
return true;
}
Comment on lines +24 to +60
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need to write this in a separate log other than syringe.log?

Comment on lines +24 to +60
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this output all hooks or only conflicts? I would expect only conflicts to be outputted, and I feel like they should always be sorted by address? not sure how the by DLL one functions, since the conflicts can (and will) be cross-dll. do we even need any sorting modes?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK let me explain :

Sorry to say that I did not redesign the hook analysis module so there might be something that didnt meet your expectations.
If you think these changes are needed, I will make a new commit for it.
Another thing is that what JSON library should we import ?
the NDJSON format output won't be there until a JSON library is chosen.

Details :

1.

why do we need to write this in a separate log other than syringe.log?

of course we can if you think this is necessary. If you want a why, it is just as it was.
If you think it should be in Syringe.log, we can change the target.

2.

what's the INJ generation for? is there some other tool that uses INJ files, or what's the premise for it?
For there was a INJ generation function.
If you think this is meaningless, a removal will be considered.

3.

is it a slow operation? is there a reason to make it optional?

I would expect the conflict detection to be always on and outputting to syringe.log

I'm sorry to say that again but it is optional because it was optional.
It's OK to let it enabled all the time.

4.

does this output all hooks or only conflicts? I would expect only conflicts to be outputted, and I feel like they should always be sorted by address? not sure how the by DLL one functions, since the conflicts can (and will) be cross-dll. do we even need any sorting modes?

this ReportLOG function is not the thing that detects conflicts.
there should be flags to control the range that ReportLOG outputs, however it isn't here.
" by DLL "just output these hooks DLL after DLL.
Sorting modes are not currently configurable.

Copy link
Copy Markdown
Author

@IronHammer-Std IronHammer-Std May 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

about --report-log :
Another thing is that TaranDahl's SKILL.md still depends on the HookAnalysis.log now and we need the adjusted logic functions similarly when rewritting it.
It enumerates hooks in the configured range. (still cannot configure it in this PR)
(an output format or output target change is OK)

about --detect-conflict :
Now the result is written in Syringe.log.
also you can change it's format and target.
It detects all cross-dll hook conflicts.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to be sorry, it's alright.

On 1 and 3 let's do it then (though I wonder if having it always on will impact startup time a lot, have you checked it?);

on 2: I mean I don't mind it in principle, I just wonder what's the specific use case?

on 4, sorry, I didn't understand what you mean. I was asking about the ByAddr and ByLib function, and I was also wondering whether the output is only conflicts or something else too.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let --detect-conflict always on .. it seems that there may be a few milliseconds and I need a further benchmark.
Most of the time is used to sort the hooks( see std::sort in the function)
And I will try and give the result.

On 2 : Currently there is no active use case but it was used before though. Besides, it's just another way to output all the hooks in a more familiar format.

On 4 : ByAddr and ByLib not only output the conflicts. They are independent of conflict detection.
In this PR, they just output all the hooks sorted by addresses and libraries respectively.
for these two arguments , they affects the behavior of --report-log flag.
ByAddr and ByLib should be enabled for various ranges but I haven't implemented these ranges in this repo because the config component is not here.


bool HookAnalyzer::ReportNDJSON()
{
//TODO
//Until a JSON library is chosen and imported
//A format may work :
//every line :
//Hook Address / Name / Source / Bytes Overridden
return true;
}

bool HookAnalyzer::HasHookConflict(bool ShowHookConflictPopup)
{
//check if there are conflicting hooks
bool Conflict = false;
for (auto& [lib, byaddr] : ByAddressEx)
{
std::vector<std::vector<HookAnalyzeData>*> 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\", At 0x%08X, From\"%s\", %d Bytes Overridden\n", v.Proc.c_str(), v.Addr, 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\", At 0x%08X, From\"%s\", %d Bytes Overridden\n", v.Proc.c_str(), v.Addr, 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;
}
Comment on lines +116 to +144
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the INJ generation for? is there some other tool that uses INJ files, or what's the premise for it?

52 changes: 52 additions & 0 deletions HookAnalyzer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#pragma once

#include<vector>
#include<unordered_map>
#include<string>

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<std::string, std::vector<HookAnalyzeData>> ByLibName;
std::unordered_map<std::string, std::vector<HookAnalyzeData>> ByLibNameEx;
public:
std::unordered_map<std::string, HookAnalyzeData> HookMap;
std::unordered_map<int, std::vector<HookAnalyzeData>> ByAddress;
std::unordered_map<std::string, HookAnalyzeData> HookMapEx;
std::unordered_map<std::string, std::unordered_map<int, std::vector<HookAnalyzeData>>> 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];
//};
59 changes: 57 additions & 2 deletions SyringeDebugger.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include "SyringeDebugger.h"
#include "SyringeDebugger.h"

#include "CRC32.h"
#include "FindFile.h"
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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__ ": 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())
Expand Down Expand Up @@ -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);
}
}
Expand Down
Loading