Skip to content

Commit 6f4bc9c

Browse files
committed
Add hookcheck utility program
1 parent f535ff1 commit 6f4bc9c

File tree

13 files changed

+1226
-0
lines changed

13 files changed

+1226
-0
lines changed

utils/hookcheck.exe

443 KB
Binary file not shown.

utils/msdia140.dll

2.19 MB
Binary file not shown.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
@echo off
2+
set "VSWHERE=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe"
3+
if exist "%VSWHERE%" goto :FindVisualStudio
4+
5+
echo Unable to locate Visual Studio: vswhere not found
6+
echo ^>^> %VSWHERE%
7+
pause
8+
exit /b 1
9+
10+
:FindVisualStudio
11+
for /f "usebackq tokens=*" %%i in (`call "%VSWHERE%" -latest -requires Microsoft.Component.MSBuild -find "**\VsDevCmd.bat"`) do (
12+
set VSDEVCMD=%%i
13+
)
14+
15+
if defined VSDEVCMD goto :SetupEnv
16+
echo Visual Studio not found
17+
echo Make sure you've installed the 'Desktop development with C++' workload
18+
pause
19+
exit /b 1
20+
21+
:SetupEnv
22+
echo Found Visual Studio environment setup batch file:
23+
echo ^>^> %VSDEVCMD%
24+
call "%VSDEVCMD%" -arch=amd64 -host_arch=amd64 >NUL
25+
26+
where MSBuild.exe >NUL 2>&1
27+
if %ERRORLEVEL% == 0 goto :Build
28+
echo MSBuild not found
29+
echo Make sure you've installed the 'Desktop development with C++' workload
30+
pause
31+
exit /b 1
32+
33+
:Build
34+
call create-solution.bat < NUL
35+
36+
MSBuild.exe build\utility.sln ^
37+
-target:hookcheck ^
38+
-property:Configuration=Release ^
39+
-property:Platform=x64 ^
40+
-maxCpuCount
41+
42+
copy build\bin\Release\*.dll ..\..\ >NUL
43+
copy build\bin\Release\hookcheck.exe ..\..\ >NUL
44+
45+
if %0 == "%~0" pause
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@echo off
2+
..\..\premake5.exe vs2022
3+
if %0 == "%~0" pause
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
/*****************************************************************************
2+
*
3+
* PROJECT: Multi Theft Auto
4+
* LICENSE: See LICENSE in the top level directory
5+
* FILE: utils/src/hookcheck/dll-analyzer.cpp
6+
* PURPOSE: Implementation for a class that can extract both the PDB file
7+
* path and the value of the __LOCAL_SIZE assignment from our
8+
* special macro.
9+
*
10+
* Multi Theft Auto is available from https://multitheftauto.com/
11+
*
12+
*****************************************************************************/
13+
14+
#include "dll-analyzer.h"
15+
16+
#include <string>
17+
#include <cstddef>
18+
#include <filesystem>
19+
20+
#define NOMINMAX
21+
#define WIN32_LEAN_AND_MEAN
22+
#include <Windows.h>
23+
24+
namespace fs = std::filesystem;
25+
26+
static const std::string EmptyString;
27+
28+
struct CV_INFO_PDB70
29+
{
30+
DWORD CvSignature;
31+
GUID Guid;
32+
DWORD Age;
33+
char PdbFileName[1];
34+
};
35+
36+
DllAnalyzer::DllAnalyzer() = default;
37+
38+
DllAnalyzer::~DllAnalyzer()
39+
{
40+
Unload();
41+
}
42+
43+
auto DllAnalyzer::LoadDll(const std::wstring& dll) -> std::pair<LoadError, HRESULT>
44+
{
45+
using enum LoadError;
46+
47+
HANDLE fileHandle = CreateFileW(dll.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
48+
49+
if (fileHandle == INVALID_HANDLE_VALUE)
50+
{
51+
const auto hr = HRESULT_FROM_WIN32(GetLastError());
52+
return std::make_pair(OpenDll, hr);
53+
}
54+
55+
HANDLE mapping = CreateFileMappingW(fileHandle, nullptr, PAGE_READONLY, 0, 0, nullptr);
56+
if (!mapping)
57+
{
58+
const auto hr = HRESULT_FROM_WIN32(GetLastError());
59+
CloseHandle(fileHandle);
60+
return std::make_pair(CreateMapping, hr);
61+
}
62+
63+
LPVOID fileView = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0);
64+
if (!fileView)
65+
{
66+
const auto hr = HRESULT_FROM_WIN32(GetLastError());
67+
CloseHandle(mapping);
68+
CloseHandle(fileHandle);
69+
return std::make_pair(CreateView, hr);
70+
}
71+
72+
const auto base = reinterpret_cast<const std::byte*>(fileView);
73+
const auto dos = reinterpret_cast<const IMAGE_DOS_HEADER*>(fileView);
74+
75+
if (dos->e_magic != IMAGE_DOS_SIGNATURE)
76+
{
77+
UnmapViewOfFile(fileView);
78+
CloseHandle(mapping);
79+
CloseHandle(fileHandle);
80+
return std::make_pair(DosSignature, E_UNEXPECTED);
81+
}
82+
83+
const auto nt = reinterpret_cast<const _IMAGE_NT_HEADERS*>(base + dos->e_lfanew);
84+
85+
if (nt->Signature != IMAGE_NT_SIGNATURE)
86+
{
87+
UnmapViewOfFile(fileView);
88+
CloseHandle(mapping);
89+
CloseHandle(fileHandle);
90+
return std::make_pair(NtSignature, E_UNEXPECTED);
91+
}
92+
93+
if (nt->FileHeader.Machine != IMAGE_FILE_MACHINE_I386 || nt->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC)
94+
{
95+
UnmapViewOfFile(fileView);
96+
CloseHandle(mapping);
97+
CloseHandle(fileHandle);
98+
return std::make_pair(ImageArch, E_UNEXPECTED);
99+
}
100+
101+
m_file = fileHandle;
102+
m_mapping = mapping;
103+
m_view = fileView;
104+
m_nt = nt;
105+
return std::make_pair(None, S_OK);
106+
}
107+
108+
void DllAnalyzer::Unload()
109+
{
110+
if (m_view)
111+
{
112+
UnmapViewOfFile(m_view);
113+
m_view = {};
114+
m_nt = {};
115+
}
116+
117+
if (m_mapping)
118+
{
119+
CloseHandle(m_mapping);
120+
m_mapping = {};
121+
}
122+
123+
if (m_file)
124+
{
125+
CloseHandle(m_file);
126+
m_file = {};
127+
}
128+
}
129+
130+
auto DllAnalyzer::ExtractPdbPath() const -> std::pair<ExtractError, std::string>
131+
{
132+
using enum ExtractError;
133+
134+
if (!m_nt)
135+
return std::make_pair(NotLoaded, EmptyString);
136+
137+
const auto base = reinterpret_cast<const std::byte*>(m_view);
138+
const auto opt32 = reinterpret_cast<const IMAGE_OPTIONAL_HEADER32*>(&m_nt->OptionalHeader);
139+
const auto ddir = &opt32->DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG];
140+
141+
if (!ddir->Size)
142+
return std::make_pair(NoDebugDirectory, EmptyString);
143+
144+
const auto rva = ddir->VirtualAddress;
145+
auto section = IMAGE_FIRST_SECTION(m_nt);
146+
147+
for (WORD i = 0; i < m_nt->FileHeader.NumberOfSections; ++i, ++section)
148+
{
149+
const auto rvaBegin = section->VirtualAddress;
150+
const auto rvaEnd = rvaBegin + section->SizeOfRawData;
151+
152+
if (rva < rvaBegin || rva >= rvaEnd)
153+
continue;
154+
155+
auto entry = reinterpret_cast<const IMAGE_DEBUG_DIRECTORY*>(base + section->PointerToRawData + (rva - rvaBegin));
156+
const DWORD numEntries = ddir->Size / sizeof(IMAGE_DEBUG_DIRECTORY);
157+
158+
for (DWORD j = 0; j < numEntries; ++j, ++entry)
159+
{
160+
if (entry->Type == IMAGE_DEBUG_TYPE_CODEVIEW)
161+
{
162+
const auto info = reinterpret_cast<const CV_INFO_PDB70*>(base + entry->PointerToRawData);
163+
164+
if (info->CvSignature == 'SDSR')
165+
{
166+
return std::make_pair(None, std::string{ info->PdbFileName });
167+
}
168+
}
169+
}
170+
171+
break;
172+
}
173+
174+
return std::make_pair(NotFound, EmptyString);
175+
}
176+
177+
auto DllAnalyzer::GetLocalSize(DWORD virtualAddress) -> std::optional<DWORD>
178+
{
179+
if (!m_nt)
180+
return std::nullopt;
181+
182+
const auto base = reinterpret_cast<const std::byte*>(m_view);
183+
auto section = IMAGE_FIRST_SECTION(m_nt);
184+
185+
for (WORD i = 0; i < m_nt->FileHeader.NumberOfSections; ++i, ++section)
186+
{
187+
const auto rvaBegin = section->VirtualAddress;
188+
const auto rvaEnd = rvaBegin + section->SizeOfRawData;
189+
190+
if (virtualAddress < rvaBegin || virtualAddress >= rvaEnd)
191+
continue;
192+
193+
// B8 00 00 00 00 | mov eax, __LOCAL_SIZE
194+
// We must skip a singular byte (0xB8 for mov) to retrieve the value of __LOCAL_SIZE.
195+
auto value = reinterpret_cast<const DWORD*>(base + section->PointerToRawData + (virtualAddress - rvaBegin) + 1);
196+
return *value;
197+
}
198+
199+
return std::nullopt;
200+
}

utils/src/hookcheck/dll-analyzer.h

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*****************************************************************************
2+
*
3+
* PROJECT: Multi Theft Auto
4+
* LICENSE: See LICENSE in the top level directory
5+
* FILE: utils/src/hookcheck/dll-analyzer.h
6+
* PURPOSE: Header for a class that can extract both the PDB file path
7+
* and the value of the __LOCAL_SIZE assignment from our special
8+
* macro.
9+
*
10+
* Multi Theft Auto is available from https://multitheftauto.com/
11+
*
12+
*****************************************************************************/
13+
14+
#pragma once
15+
16+
#include <utility>
17+
#include <string>
18+
#include <optional>
19+
20+
using HRESULT = long;
21+
using HANDLE = void*;
22+
using LPVOID = void*;
23+
using DWORD = unsigned long;
24+
using ULONGLONG = unsigned long long;
25+
26+
struct _IMAGE_NT_HEADERS;
27+
28+
class DllAnalyzer final
29+
{
30+
public:
31+
enum class LoadError
32+
{
33+
None,
34+
OpenDll,
35+
CreateMapping,
36+
CreateView,
37+
DosSignature,
38+
NtSignature,
39+
ImageArch,
40+
};
41+
42+
enum class ExtractError
43+
{
44+
None,
45+
NotLoaded,
46+
NoDebugDirectory,
47+
NotFound,
48+
};
49+
50+
public:
51+
DllAnalyzer();
52+
~DllAnalyzer();
53+
54+
DllAnalyzer(const DllAnalyzer&) = delete;
55+
DllAnalyzer& operator=(const DllAnalyzer&) = delete;
56+
57+
DllAnalyzer(DllAnalyzer&&) noexcept = default;
58+
DllAnalyzer& operator=(DllAnalyzer&&) noexcept = default;
59+
60+
public:
61+
/**
62+
* @brief Loads a DLL (Dynamic-Link Library) file.
63+
* @param dll The path to the DLL file to load.
64+
* @return A pair consisting of a LoadError value indicating the generic error of the load operation,
65+
* and an HRESULT code providing additional status information.
66+
*/
67+
[[nodiscard]]
68+
auto LoadDll(const std::wstring& dll) -> std::pair<LoadError, HRESULT>;
69+
70+
/**
71+
* @brief Releases or unloads resources.
72+
*/
73+
void Unload();
74+
75+
/**
76+
* @brief Extracts the path to the PDB (Program Database) file.
77+
* @return A pair consisting of a ExtractError value indicating an error and an empty string on failure,
78+
* or a pair consisting of ExtractError::None and the path to the PDB file on success.
79+
*/
80+
[[nodiscard]]
81+
auto ExtractPdbPath() const -> std::pair<ExtractError, std::string>;
82+
83+
/**
84+
* @brief Retrieves the value of __LOCAL_SIZE using the virtual address of the label before it.
85+
* @param virtualAddress The virtual address of the label before the assignment.
86+
* @return The value of __LOCAL_SIZE on success, otherwise an empty value on failure.
87+
*/
88+
[[nodiscard]]
89+
auto GetLocalSize(DWORD virtualAddress) -> std::optional<DWORD>;
90+
91+
private:
92+
HANDLE m_file{};
93+
HANDLE m_mapping{};
94+
LPVOID m_view{};
95+
96+
const _IMAGE_NT_HEADERS* m_nt{};
97+
};

0 commit comments

Comments
 (0)