├── .gitattributes ├── Hooking.Patterns.cpp ├── Hooking.Patterns.h ├── LICENSE.md └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /Hooking.Patterns.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the CitizenFX project - http://citizen.re/ 3 | * 4 | * See LICENSE and MENTIONS in the root of the source tree for information 5 | * regarding licensing. 6 | */ 7 | 8 | #include "Hooking.Patterns.h" 9 | 10 | #define WIN32_LEAN_AND_MEAN 11 | #define NOMINMAX 12 | #include 13 | #include 14 | 15 | #if PATTERNS_USE_HINTS 16 | #include 17 | #endif 18 | 19 | 20 | #if PATTERNS_USE_HINTS 21 | 22 | // from boost someplace 23 | template 24 | struct basic_fnv_1 25 | { 26 | std::uint64_t operator()(std::string_view text) const 27 | { 28 | std::uint64_t hash = OffsetBasis; 29 | for (auto it : text) 30 | { 31 | hash *= FnvPrime; 32 | hash ^= it; 33 | } 34 | 35 | return hash; 36 | } 37 | }; 38 | 39 | static constexpr std::uint64_t fnv_prime = 1099511628211u; 40 | static constexpr std::uint64_t fnv_offset_basis = 14695981039346656037u; 41 | 42 | typedef basic_fnv_1 fnv_1; 43 | 44 | #endif 45 | 46 | namespace hook 47 | { 48 | 49 | ptrdiff_t details::get_process_base() 50 | { 51 | return ptrdiff_t(GetModuleHandle(nullptr)); 52 | } 53 | 54 | 55 | #if PATTERNS_USE_HINTS 56 | static auto& getHints() 57 | { 58 | static std::multimap hints; 59 | return hints; 60 | } 61 | #endif 62 | 63 | static void TransformPattern(std::string_view pattern, std::basic_string& data, std::basic_string& mask) 64 | { 65 | uint8_t tempDigit = 0; 66 | bool tempFlag = false; 67 | 68 | auto tol = [] (char ch) -> uint8_t 69 | { 70 | if (ch >= 'A' && ch <= 'F') return uint8_t(ch - 'A' + 10); 71 | if (ch >= 'a' && ch <= 'f') return uint8_t(ch - 'a' + 10); 72 | return uint8_t(ch - '0'); 73 | }; 74 | 75 | for (auto ch : pattern) 76 | { 77 | if (ch == ' ') 78 | { 79 | continue; 80 | } 81 | else if (ch == '?') 82 | { 83 | data.push_back(0); 84 | mask.push_back(0); 85 | } 86 | else if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f')) 87 | { 88 | uint8_t thisDigit = tol(ch); 89 | 90 | if (!tempFlag) 91 | { 92 | tempDigit = thisDigit << 4; 93 | tempFlag = true; 94 | } 95 | else 96 | { 97 | tempDigit |= thisDigit; 98 | tempFlag = false; 99 | 100 | data.push_back(tempDigit); 101 | mask.push_back(0xFF); 102 | } 103 | } 104 | } 105 | } 106 | 107 | class executable_meta 108 | { 109 | private: 110 | uintptr_t m_begin; 111 | uintptr_t m_end; 112 | 113 | template 114 | TReturn* getRVA(TOffset rva) 115 | { 116 | return (TReturn*)(m_begin + rva); 117 | } 118 | 119 | public: 120 | explicit executable_meta(uintptr_t module) 121 | : m_begin(module), m_end(0) 122 | { 123 | static auto getSection = [](const PIMAGE_NT_HEADERS nt_headers, WORD section) -> PIMAGE_SECTION_HEADER 124 | { 125 | return reinterpret_cast( 126 | (UCHAR*)nt_headers->OptionalHeader.DataDirectory + 127 | nt_headers->OptionalHeader.NumberOfRvaAndSizes * sizeof(IMAGE_DATA_DIRECTORY) + 128 | section * sizeof(IMAGE_SECTION_HEADER)); 129 | }; 130 | 131 | PIMAGE_DOS_HEADER dosHeader = getRVA(0); 132 | PIMAGE_NT_HEADERS ntHeader = getRVA(dosHeader->e_lfanew); 133 | 134 | for (WORD i = 0; i < ntHeader->FileHeader.NumberOfSections; i++) 135 | { 136 | auto sec = getSection(ntHeader, i); 137 | auto secSize = sec->SizeOfRawData != 0 ? sec->SizeOfRawData : sec->Misc.VirtualSize; 138 | if (sec->Characteristics & IMAGE_SCN_MEM_EXECUTE) 139 | m_end = m_begin + sec->VirtualAddress + secSize; 140 | if ((i == ntHeader->FileHeader.NumberOfSections - 1) && m_end == 0) 141 | m_end = m_begin + sec->PointerToRawData + secSize; 142 | } 143 | } 144 | 145 | executable_meta(uintptr_t begin, uintptr_t end) 146 | : m_begin(begin), m_end(end) 147 | { 148 | } 149 | 150 | inline uintptr_t begin() const { return m_begin; } 151 | inline uintptr_t end() const { return m_end; } 152 | }; 153 | 154 | namespace details 155 | { 156 | 157 | void basic_pattern_impl::Initialize(std::string_view pattern) 158 | { 159 | // get the hash for the base pattern 160 | #if PATTERNS_USE_HINTS 161 | m_hash = fnv_1()(pattern); 162 | #endif 163 | 164 | // transform the base pattern from IDA format to canonical format 165 | TransformPattern(pattern, m_bytes, m_mask); 166 | 167 | #if PATTERNS_USE_HINTS 168 | // if there's hints, try those first 169 | #if PATTERNS_CAN_SERIALIZE_HINTS 170 | if (m_rangeStart == reinterpret_cast(GetModuleHandle(nullptr))) 171 | #endif 172 | { 173 | auto range = getHints().equal_range(m_hash); 174 | 175 | if (range.first != range.second) 176 | { 177 | std::for_each(range.first, range.second, [&] (const auto& hint) 178 | { 179 | ConsiderHint(hint.second); 180 | }); 181 | 182 | // if the hints succeeded, we don't need to do anything more 183 | if (!m_matches.empty()) 184 | { 185 | m_matched = true; 186 | return; 187 | } 188 | } 189 | } 190 | #endif 191 | } 192 | 193 | void basic_pattern_impl::EnsureMatches(uint32_t maxCount) 194 | { 195 | if (m_matched || (!m_rangeStart && !m_rangeEnd)) 196 | { 197 | return; 198 | } 199 | 200 | // scan the executable for code 201 | executable_meta executable = m_rangeStart != 0 && m_rangeEnd != 0 ? executable_meta(m_rangeStart, m_rangeEnd) : executable_meta(m_rangeStart); 202 | 203 | auto matchSuccess = [&] (uintptr_t address) 204 | { 205 | #if PATTERNS_USE_HINTS 206 | getHints().emplace(m_hash, address); 207 | #else 208 | (void)address; 209 | #endif 210 | 211 | return (m_matches.size() == maxCount); 212 | }; 213 | 214 | const uint8_t* pattern = m_bytes.data(); 215 | const uint8_t* mask = m_mask.data(); 216 | const size_t maskSize = m_mask.size(); 217 | const size_t lastWild = m_mask.find_last_not_of(uint8_t(0xFF)); 218 | 219 | ptrdiff_t Last[256]; 220 | 221 | std::fill(std::begin(Last), std::end(Last), lastWild == std::string::npos ? -1 : static_cast(lastWild) ); 222 | 223 | for ( ptrdiff_t i = 0; i < static_cast(maskSize); ++i ) 224 | { 225 | if ( Last[ pattern[i] ] < i ) 226 | { 227 | Last[ pattern[i] ] = i; 228 | } 229 | } 230 | 231 | __try 232 | { 233 | for (uintptr_t i = executable.begin(), end = executable.end() - maskSize; i <= end;) 234 | { 235 | uint8_t* ptr = reinterpret_cast(i); 236 | ptrdiff_t j = maskSize - 1; 237 | 238 | while ((j >= 0) && pattern[j] == (ptr[j] & mask[j])) j--; 239 | 240 | if (j < 0) 241 | { 242 | m_matches.emplace_back(ptr); 243 | 244 | if (matchSuccess(i)) 245 | { 246 | break; 247 | } 248 | i++; 249 | } 250 | else i += std::max(ptrdiff_t(1), j - Last[ptr[j]]); 251 | } 252 | } 253 | __except ((GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) 254 | { 255 | } 256 | 257 | m_matched = true; 258 | } 259 | 260 | bool basic_pattern_impl::ConsiderHint(uintptr_t offset) 261 | { 262 | uint8_t* ptr = reinterpret_cast(offset); 263 | 264 | #if PATTERNS_CAN_SERIALIZE_HINTS 265 | const uint8_t* pattern = m_bytes.data(); 266 | const uint8_t* mask = m_mask.data(); 267 | 268 | for (size_t i = 0, j = m_mask.size(); i < j; i++) 269 | { 270 | if (pattern[i] != (ptr[i] & mask[i])) 271 | { 272 | return false; 273 | } 274 | } 275 | #endif 276 | 277 | m_matches.emplace_back(ptr); 278 | 279 | return true; 280 | } 281 | 282 | #if PATTERNS_USE_HINTS && PATTERNS_CAN_SERIALIZE_HINTS 283 | void basic_pattern_impl::hint(uint64_t hash, uintptr_t address) 284 | { 285 | auto& hints = getHints(); 286 | 287 | auto range = hints.equal_range(hash); 288 | 289 | for (auto it = range.first; it != range.second; ++it) 290 | { 291 | if (it->second == address) 292 | { 293 | return; 294 | } 295 | } 296 | 297 | hints.emplace(hash, address); 298 | } 299 | #endif 300 | 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /Hooking.Patterns.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the CitizenFX project - http://citizen.re/ 3 | * 4 | * See LICENSE and MENTIONS in the root of the source tree for information 5 | * regarding licensing. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #if defined(_CPPUNWIND) && !defined(PATTERNS_SUPPRESS_EXCEPTIONS) 17 | #define PATTERNS_ENABLE_EXCEPTIONS 18 | #endif 19 | 20 | namespace hook 21 | { 22 | struct assert_err_policy 23 | { 24 | static void count([[maybe_unused]] bool countMatches) { assert(countMatches); } 25 | }; 26 | 27 | #ifdef PATTERNS_ENABLE_EXCEPTIONS 28 | class txn_exception 29 | { 30 | // Deliberately empty for now 31 | }; 32 | 33 | #define TXN_CATCH() catch (const hook::txn_exception&) {} 34 | 35 | struct exception_err_policy 36 | { 37 | static void count(bool countMatches) { if (!countMatches) { throw txn_exception{}; } } 38 | }; 39 | #else 40 | struct exception_err_policy 41 | { 42 | }; 43 | #endif 44 | 45 | class pattern_match 46 | { 47 | private: 48 | void* m_pointer; 49 | 50 | public: 51 | inline pattern_match(void* pointer) 52 | : m_pointer(pointer) 53 | { 54 | } 55 | 56 | template 57 | T* get(ptrdiff_t offset = 0) const 58 | { 59 | char* ptr = reinterpret_cast(m_pointer); 60 | return reinterpret_cast(ptr + offset); 61 | } 62 | }; 63 | 64 | namespace details 65 | { 66 | ptrdiff_t get_process_base(); 67 | 68 | class basic_pattern_impl 69 | { 70 | protected: 71 | std::basic_string m_bytes; 72 | std::basic_string m_mask; 73 | 74 | #if PATTERNS_USE_HINTS 75 | uint64_t m_hash = 0; 76 | #endif 77 | 78 | std::vector m_matches; 79 | 80 | bool m_matched = false; 81 | 82 | uintptr_t m_rangeStart; 83 | uintptr_t m_rangeEnd; 84 | 85 | protected: 86 | void Initialize(std::string_view pattern); 87 | 88 | bool ConsiderHint(uintptr_t offset); 89 | 90 | void EnsureMatches(uint32_t maxCount); 91 | 92 | inline pattern_match _get_internal(size_t index) const 93 | { 94 | return m_matches[index]; 95 | } 96 | 97 | private: 98 | explicit basic_pattern_impl(uintptr_t begin, uintptr_t end = 0) 99 | : m_rangeStart(begin), m_rangeEnd(end) 100 | { 101 | } 102 | 103 | public: 104 | explicit basic_pattern_impl() 105 | : m_rangeStart(0), m_rangeEnd(0) 106 | { 107 | } 108 | 109 | explicit basic_pattern_impl(std::string_view pattern) 110 | : basic_pattern_impl(get_process_base()) 111 | { 112 | Initialize(std::move(pattern)); 113 | } 114 | 115 | inline basic_pattern_impl(void* module, std::string_view pattern) 116 | : basic_pattern_impl(reinterpret_cast(module)) 117 | { 118 | Initialize(std::move(pattern)); 119 | } 120 | 121 | inline basic_pattern_impl(uintptr_t begin, uintptr_t end, std::string_view pattern) 122 | : basic_pattern_impl(begin, end) 123 | { 124 | Initialize(std::move(pattern)); 125 | } 126 | 127 | // Pretransformed patterns 128 | inline basic_pattern_impl(std::basic_string_view bytes, std::basic_string_view mask) 129 | : basic_pattern_impl(get_process_base()) 130 | { 131 | assert( bytes.length() == mask.length() ); 132 | m_bytes = std::move(bytes); 133 | m_mask = std::move(mask); 134 | } 135 | 136 | protected: 137 | #if PATTERNS_USE_HINTS && PATTERNS_CAN_SERIALIZE_HINTS 138 | // define a hint 139 | static void hint(uint64_t hash, uintptr_t address); 140 | #endif 141 | }; 142 | } 143 | 144 | template 145 | class basic_pattern : details::basic_pattern_impl 146 | { 147 | public: 148 | using details::basic_pattern_impl::basic_pattern_impl; 149 | 150 | inline basic_pattern&& count(uint32_t expected) 151 | { 152 | EnsureMatches(expected); 153 | err_policy::count(m_matches.size() == expected); 154 | return std::forward(*this); 155 | } 156 | 157 | inline basic_pattern&& count_hint(uint32_t expected) 158 | { 159 | EnsureMatches(expected); 160 | return std::forward(*this); 161 | } 162 | 163 | inline basic_pattern&& clear(void* module = nullptr) 164 | { 165 | if (module) 166 | { 167 | this->m_rangeStart = reinterpret_cast(module); 168 | this->m_rangeEnd = 0; 169 | } 170 | 171 | m_matches.clear(); 172 | m_matched = false; 173 | return std::forward(*this); 174 | } 175 | 176 | inline size_t size() 177 | { 178 | EnsureMatches(UINT32_MAX); 179 | return m_matches.size(); 180 | } 181 | 182 | inline bool empty() 183 | { 184 | return size() == 0; 185 | } 186 | 187 | inline pattern_match get(size_t index) 188 | { 189 | EnsureMatches(UINT32_MAX); 190 | return _get_internal(index); 191 | } 192 | 193 | inline pattern_match get_one() 194 | { 195 | return std::forward(*this).count(1)._get_internal(0); 196 | } 197 | 198 | template 199 | inline auto get_first(ptrdiff_t offset = 0) 200 | { 201 | return get_one().template get(offset); 202 | } 203 | 204 | template 205 | inline Pred for_each_result(Pred&& pred) 206 | { 207 | EnsureMatches(UINT32_MAX); 208 | for ( auto it : m_matches ) 209 | { 210 | std::forward(pred)(it); 211 | } 212 | return std::forward(pred); 213 | } 214 | 215 | public: 216 | #if PATTERNS_USE_HINTS && PATTERNS_CAN_SERIALIZE_HINTS 217 | // define a hint 218 | static void hint(uint64_t hash, uintptr_t address) 219 | { 220 | details::basic_pattern_impl::hint(hash, address); 221 | } 222 | #endif 223 | }; 224 | 225 | using pattern = basic_pattern; 226 | 227 | inline auto make_module_pattern(void* module, std::string_view bytes) 228 | { 229 | return pattern(module, std::move(bytes)); 230 | } 231 | 232 | inline auto make_range_pattern(uintptr_t begin, uintptr_t end, std::string_view bytes) 233 | { 234 | return pattern(begin, end, std::move(bytes)); 235 | } 236 | 237 | template 238 | inline auto get_pattern(std::string_view pattern_string, ptrdiff_t offset = 0) 239 | { 240 | return pattern(std::move(pattern_string)).get_first(offset); 241 | } 242 | 243 | inline auto module_pattern(void* module, std::string_view bytes) 244 | { 245 | return make_module_pattern(module, std::move(bytes)); 246 | } 247 | 248 | inline auto range_pattern(uintptr_t begin, uintptr_t end, std::string_view bytes) 249 | { 250 | return make_range_pattern(begin, end, std::move(bytes)); 251 | } 252 | 253 | namespace txn 254 | { 255 | using pattern = hook::basic_pattern; 256 | 257 | inline auto make_module_pattern(void* module, std::string_view bytes) 258 | { 259 | return pattern(module, std::move(bytes)); 260 | } 261 | 262 | inline auto make_range_pattern(uintptr_t begin, uintptr_t end, std::string_view bytes) 263 | { 264 | return pattern(begin, end, std::move(bytes)); 265 | } 266 | 267 | template 268 | inline auto get_pattern(std::string_view pattern_string, ptrdiff_t offset = 0) 269 | { 270 | return pattern(std::move(pattern_string)).get_first(offset); 271 | } 272 | 273 | inline auto module_pattern(void* module, std::string_view bytes) 274 | { 275 | return make_module_pattern(module, std::move(bytes)); 276 | } 277 | 278 | inline auto range_pattern(uintptr_t begin, uintptr_t end, std::string_view bytes) 279 | { 280 | return make_range_pattern(begin, end, std::move(bytes)); 281 | } 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Bas Timmer/NTAuthority et al. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Hooking.Patterns 2 | ---------------- 3 | Sample: 4 | 5 | ```cpp 6 | #include "stdafx.h" 7 | #include 8 | #include "Hooking.Patterns.h" 9 | 10 | int main() 11 | { 12 | auto pattern = hook::pattern("54 68 69 73 20 70 72 6F 67 72"); 13 | if (!pattern.count_hint(1).empty()) 14 | { 15 | auto text = pattern.get(0).get(0); 16 | MessageBoxA(0, text, text, 0); 17 | } 18 | return 0; 19 | } 20 | ``` 21 | 22 | Result: 23 | 24 | ![MessageBox](http://i.imgur.com/Tuijf2I.png) 25 | --------------------------------------------------------------------------------