VoltMod
C++23 framework for CS2 server plugins
Loading...
Searching...
No Matches
SigScanner.cpp
Go to the documentation of this file.
2
4
6#include <cstddef>
7#include <cstdint>
8#include <cstdio>
9#include <cstring>
10#include <format>
11#include <fstream>
12#include <map>
13#include <optional>
14#include <string>
15#include <utility>
16#include <vector>
17
18#ifdef _WIN32
19// The page-protection probe uses the Windows API names directly.
20#include <windows.h>
21#endif
22
23namespace VoltMod
24{
25
26/**
27 * Byte frequencies are stable for a mapped module, so cache them by base address. Count explicit
28 * scan ranges because a Linux module's overall span may include unmapped gaps.
29 */
30static const ByteHistogram& FrequenciesOf(const Image& module, const std::vector<ScanRange>& ranges)
31{
32 static std::map<const uint8_t*, ByteHistogram> cache;
33
34 const auto found = cache.find(module.Base);
35 if (found != cache.end())
36 {
37 return found->second;
38 }
39
41 for (const auto& range : ranges)
42 {
43 CountBytes(range.Base, range.Size, counts);
44 }
45
46 return cache.emplace(module.Base, counts).first->second;
47}
48
49std::string PlatformModuleName(std::string_view moduleName)
50{
51#ifdef _WIN32
52 return std::format("{}.dll", moduleName);
53#else
54 return std::format("lib{}.so", moduleName);
55#endif
56}
57
58bool FindImage(std::string_view moduleName, Image& module)
59{
60 std::vector<ScanRange> ranges;
62}
63
64ScanResult FindPatternEx(std::string_view moduleName, const std::string& pattern)
65{
66 const std::string fullName = PlatformModuleName(moduleName);
67
68 Image module;
69 std::vector<ScanRange> ranges;
71 {
72 Log::Error("SigScanner: Module '{}' not found.", fullName);
73 return {};
74 }
75
76 const std::vector<PatternByte> bytes = ParsePattern(pattern);
78
79 const uint8_t* first = nullptr;
80 for (const auto& range : ranges)
81 {
82 // Continue after a hit so an ambiguous pattern is not bound to the first match.
83 for (size_t at = 0; at < range.Size;)
84 {
85 const uint8_t* hit = FindFirst(range.Base + at, range.Size - at, bytes, anchor);
86 if (!hit)
87 {
88 break;
89 }
90
91 if (first)
92 {
93 Log::Warn("SigScanner: Pattern ambiguous in '{}' (2+ matches); refusing it.", fullName);
94 return {const_cast<uint8_t*>(first), false, std::move(module)};
95 }
96 first = hit;
97 at = static_cast<size_t>(hit - range.Base) + 1;
98 }
99 }
100
101 if (!first)
102 {
103 Log::Warn("SigScanner: Pattern not found in '{}'.", fullName);
104 }
105 return {const_cast<uint8_t*>(first), true, std::move(module)};
106}
107
109{
110 if (matchAddress == 0 || !module.Base)
111 {
112 return 0;
113 }
114
115 // The displacement must be inside the mapping before it is read.
116 if (!Rel32ReadInBounds(reinterpret_cast<uintptr_t>(module.Base), module.Size, matchAddress, ripOffset))
117 {
118 return 0;
119 }
120
123 std::memcpy(&displacement, reinterpret_cast<const void*>(site), sizeof(displacement));
125}
126
128{
129 const uint8_t* End = nullptr;
130 bool Readable = false;
131 bool Executable = false;
132};
133
134static std::optional<MemoryRegion> QueryRegion(const void* address)
135{
136#ifdef _WIN32
138 if (VirtualQuery(address, &info, sizeof(info)) != sizeof(info) || info.State != MEM_COMMIT ||
139 (info.Protect & PAGE_GUARD) != 0)
140 {
141 return std::nullopt;
142 }
143
147 return MemoryRegion{.End = static_cast<const uint8_t*>(info.BaseAddress) + info.RegionSize,
148 .Readable = (info.Protect & readable) != 0,
149 .Executable = (info.Protect & executable) != 0};
150#else
151 const auto target = reinterpret_cast<unsigned long>(address);
152 std::ifstream maps("/proc/self/maps");
153 std::string line;
154 while (std::getline(maps, line))
155 {
156 unsigned long start = 0;
157 unsigned long end = 0;
158 char perms[5] = {};
159 if (std::sscanf(line.c_str(), "%lx-%lx %4s", &start, &end, perms) != 3)
160 {
161 continue;
162 }
163 if (target >= start && target < end)
164 {
165 return MemoryRegion{.End = reinterpret_cast<const uint8_t*>(end),
166 .Readable = perms[0] == 'r',
167 .Executable = perms[2] == 'x'};
168 }
169 }
170 return std::nullopt;
171#endif
172}
173
175{
176 if (!address)
177 {
178 return false;
179 }
180
181 const auto region = QueryRegion(address);
182 return region && region->Executable;
183}
184
185bool IsReadableAddress(const void* address, size_t bytes)
186{
187 if (!address || bytes == 0)
188 {
189 return false;
190 }
191
192 // Keep the span within one mapping because the next may be unmapped.
193 const auto region = QueryRegion(address);
194 return region && region->Readable &&
195 bytes <= static_cast<size_t>(region->End - static_cast<const uint8_t*>(address));
196}
197
198} // namespace VoltMod
Parsing and searching byte patterns, with no notion of a loaded module.
void Error(std::format_string< Args... > fmt, Args &&... args)
Definition Log.hpp:97
void Warn(std::format_string< Args... > fmt, Args &&... args)
Definition Log.hpp:88
bool FindImage(std::string_view moduleName, Image &module)
std::vector< PatternByte > ParsePattern(const std::string &pattern)
static const ByteHistogram & FrequenciesOf(const Image &module, const std::vector< ScanRange > &ranges)
bool FindModuleAndRanges(std::string_view fileName, Image &module, std::vector< ScanRange > &ranges)
ScanResult FindPatternEx(std::string_view moduleName, const std::string &pattern)
const uint8_t * FindFirst(const uint8_t *base, size_t size, const std::vector< PatternByte > &pattern, size_t anchor)
static std::string ReadFile(const std::filesystem::path &path)
Definition Loader.cpp:56
bool IsExecutableAddress(const void *address)
bool IsReadableAddress(const void *address, size_t bytes)
uintptr_t ResolveRelativeAddress(const Image &module, uintptr_t matchAddress, int ripOffset, int ripSize)
size_t AnchorOf(const std::vector< PatternByte > &pattern, const ByteHistogram &frequencies)
constexpr bool Rel32ReadInBounds(uintptr_t moduleBase, size_t moduleSize, uintptr_t matchAddress, int ripOffset) noexcept
std::array< size_t, 256 > ByteHistogram
static std::optional< MemoryRegion > QueryRegion(const void *address)
constexpr uintptr_t Rel32Target(uintptr_t site, int32_t displacement, int ripSize=Rel32Size) noexcept
void CountBytes(const uint8_t *base, size_t size, ByteHistogram &counts)
std::string PlatformModuleName(std::string_view moduleName)
constexpr uintptr_t Rel32Site(uintptr_t matchAddress, int ripOffset) noexcept
const uint8_t * End