Initial commit

This commit is contained in:
2024-09-13 23:01:27 +08:00
commit 4e07e943a4
9 changed files with 2150 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
build
*.txt
*.zip

21
3rdparty/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Rokas Kupstys
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

1462
3rdparty/hooker.c vendored Normal file

File diff suppressed because it is too large Load Diff

162
3rdparty/hooker.h vendored Normal file
View File

@@ -0,0 +1,162 @@
/*
* MIT License
*
* Copyright (c) 2017 Rokas Kupstys
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#define HOOKER_ERROR (false)
#define HOOKER_SUCCESS (true)
#define HOOKER_MEM_R (1 << 0)
#define HOOKER_MEM_W (1 << 1)
#define HOOKER_MEM_X (1 << 2)
#define HOOKER_MEM_RW (HOOKER_MEM_R|HOOKER_MEM_W)
#define HOOKER_MEM_RX (HOOKER_MEM_R|HOOKER_MEM_X)
#define HOOKER_MEM_RWX (HOOKER_MEM_R|HOOKER_MEM_W|HOOKER_MEM_X)
/// Memory protection flags are platform specific (not a combination of above flags) and should not be converted.
#define HOOKER_MEM_PLATFORM (1 << 31)
/// Write a call instruction (5 bytes on x86/64).
#define HOOKER_HOOK_CALL (1 << 0)
/// Write a jump instruction (5 bytes on x86/64).
#define HOOKER_HOOK_JMP (1 << 1)
/// Use fat jump (16 for call and 14 bytes for jmp on x64). Has no effect on x86.
#define HOOKER_HOOK_FAT (1 << 2)
#if __cplusplus
extern "C" {
#endif
/// Call any address with arbitrary amount of arguments. Returns value of specified type.
/// \param returnType of return value.
/// \param address of a call.
#define hooker_call(returnType, address, ...) (returnType(*)(...))(address)(__VA_ARGS__);
/// Call any address with arbitrary amount of arguments. Does not return anything.
/// \param address of a call.
#define hooker_callv(address, ...) (void(*)(...))(address)(__VA_ARGS__);
/// Change protection of memory range.
/// \param p memory address.
/// \param size of memory at address p.
/// \param protection a combination of HOOKER_MEM_* flags.
/// \param original_protection on supported platforms will be set to current memory protection mode. May be null. If not null - always initialize to a best-guess current protection flags value, because on some platforms (like linux) this variable will not be set.
bool hooker_mem_protect(void* p, size_t size, size_t protection, size_t* original_protection);
/// Get mnemonic size of current platform.
size_t hooker_get_mnemonic_size(void* address, size_t min_size);
/// Hotpatch a call.
void* hooker_hotpatch(void* location, void* new_proc);
/// Unhotpatch a call.
bool hooker_unhotpatch(void* location);
/// Writes a jump or call hook from `address` to `new_proc`.
/// \param address a pointer where hook should be written
/// \param new_proc a pointer where hook should point to.
/// \param flags any of HOOKER_HOOK_* flags. They may not be combined.
/// \param nops of bytes to nop after hook instruction. Specify -1 to autocalculate.
/// \returns null on failure or non-null on success.
bool hooker_write_instruction(void* address, void* new_proc, size_t flags, size_t nops);
/// Writes a 5 byte jump with E9 opcode. Difference between pointers is limited to 32bit.
/// \param address a pointer where hook should be written
/// \param new_proc a pointer where hook should point to.
/// \returns null on failure or non-null on success.
bool hooker_write_jmp(void* address, void* new_proc);
/// Writes a 5 byte call with E8 opcode. Difference between pointers is limited to 32bit.
/// \param address a pointer where hook should be written
/// \param new_proc a pointer where hook should point to.
/// \returns null on failure or non-null on success.
bool hooker_write_call(void* address, void* new_proc);
/// Redirect call to custom proc.
/// \param address a start of original call. Warning: It should not contain any relatively-addressed instructions like calls or jumps.
/// \param new_proc a proc that will be called instead of original one.
/// \returns pointer, calling which will invoke original proc. It is user's responsibility to call original code when necessary.
void* hooker_hook(void* address, void* new_proc, size_t flags);
/// Unhook a hook created by hooker_hook(.., .., HOOKER_HOOK_REDIRECT, ..).
/// \param address where hook was written to.
/// \param original result of hooker_hook() call.
void hooker_unhook(void* address, void* original);
/// Return address in object's vmt which is pointing to specified method.
/// \param object is a pointer to a c++ object.
/// \param method is a pointer to a c++ object method.
size_t* hooker_get_vmt_address(void* object, void* method);
/// Find a first occurrence of memory pattern.
/// \param start a pointer to beginning of memory range.
/// \param size a size of memory range. If size is 0 then entire memory space will be searched. If pattern does not exist this will likely result in a crash. Negative size will search backwards.
/// \param pattern a array of bytes to search for.
/// \param pattern_len a length of pattern array.
/// \param wildcard byte in the pattern array. It must be of same size as indicated by `pattern_len`.
void* hooker_find_pattern(void* start, int size, const uint8_t* pattern, size_t pattern_len, uint8_t wildcard);
/// Find a first occurrence of memory pattern.
/// \param start a pointer to beginning of memory range.
/// \param size a size of memory range. If size is 0 then entire memory space will be searched. If pattern does not exist this will likely result in a crash. Negative size will search backwards.
/// \param pattern a array of bytes to search for.
/// \param pattern_len a length of pattern array.
/// \param wildcard array where values may be one of: 0? = 1, ?0 = 2, ?? = 3.
void* hooker_find_pattern_ex(void* start, int size, const uint8_t* pattern, size_t pattern_len, const uint8_t* wildcard);
/// Fill memory with nops (0x90 opcode).
/// \param start of the memory address.
/// \param size of the memory that will be filled.
/// \returns HOOKER_SUCCESS or HOOKER_ERROR.
bool hooker_nop(void* start, size_t size);
/// Write bytes to specified memory address.
/// \param start of the memory address.
/// \param data to be written.
/// \param size of data.
bool hooker_write(void* start, void* data, size_t size);
/// Searches for symbol in specified library. On Windows LoadLibrary() will be called if its not loaded yet, otherwise GetModuleHandle() will be used.
/// On linux dlopen(RTLD_NODELETE) and dlclose() will always be called.
/// \param lib_name string with dynamic library name.
/// \param sym_name string with exported symbol name.
/// \returns pointer to resolved dynamic symbol.
void* hooker_dlsym(const char* lib_name, const char* sym_name);
#if _WIN32
/// Replaces entry in import address table of specified module.
/// \param mod_name string with name of module whose import table is to be modified.
/// \param imp_mod_name string with name of module which module specified in `mod_name` imports.
/// \param imp_proc_name string with name of symbol imported from module specified in `imp_mod_name`.
/// \param new_proc a pointer that should replace old entry in import address table.
/// \returns original value that was in import address table or null pointer on failure.
void* hooker_hook_iat(const char* mod_name, const char* imp_mod_name, const char* imp_proc_name, void* new_proc);
#endif
// Define following macro in a single translation unit in order to use library without building it.
#ifdef HOOKER_IMPLEMENTATION
# include "hooker.c"
#endif // HOOKER_IMPLEMENTATION
#if __cplusplus
};
#endif

354
3rdparty/hooker.hpp vendored Normal file
View File

@@ -0,0 +1,354 @@
/*
* MIT License
*
* Copyright (c) 2017 Rokas Kupstys
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#include <type_traits>
#include <stdexcept>
#include <cstdint>
namespace hooker
{
namespace detail
{
extern "C"
{
#include "hooker.h"
}
struct Helper { };
}
#if __cplusplus >= 201402L
/// Pattern for find_pattern() function.
template <size_t N>
struct pattern
{
/// Bytes to find. Value of wildcard byte or byte half can be anything.
uint8_t pattern[N];
/// Wildcard pattern. Byte value may be one of: 0? = 1, ?0 = 2, ?? = 3.
uint8_t wildcard[N];
};
# define CPP14(x) x
namespace detail
{
// Convert hex character to a number.
constexpr uint8_t char_to_byte(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
else if (c >= 'a' && c <= 'f')
return 0x0A + c - 'a';
else if (c >= 'A' && c <= 'F')
return 0x0A + c - 'A';
else if (c == '?')
return 0;
else
throw std::runtime_error("Not a hex character.");
}
// Convert text hex byte at `idx` to binary.
template <size_t N>
constexpr uint8_t get_pattern_byte(const char(&s)[N], size_t idx)
{
if (s[idx * 3 + 2] != ' ' && s[idx * 3 + 2] != '\0')
throw std::runtime_error("Improperly formatted pattern.");
else
return (char_to_byte(s[idx * 3]) << 4) | char_to_byte(s[idx * 3 + 1]);
}
// Convert text wildcard to binary mask.
template <size_t N>
constexpr uint8_t get_wildcard_byte(const char(&s)[N], size_t idx)
{
return (s[idx * 3] == '?' ? 2 : 0) | (s[idx * 3 + 1] == '?' ? 1 : 0);
}
// Convert a character array to binary version and wildcard array.
template <size_t N, size_t... Is>
constexpr pattern<sizeof...(Is)> decode_string_pattern(const char(&s)[N], std::index_sequence<Is...>)
{
return {
{ get_pattern_byte(s, Is)... },
{ get_wildcard_byte(s, Is)... },
};
}
}
// Create a binary pattern from raw string litteral in format: "AB C? ?D ??".
template <size_t N>
constexpr const pattern<N / 3> mkpat(const char(&s)[N])
{
if ((N % 3) == 0)
return detail::decode_string_pattern(s, std::make_index_sequence<N / 3>());
else
throw std::runtime_error("Improperly formatted pattern.");
}
#else
# define CPP14(x)
#endif
#if __cplusplus > 201703L
using bit_cast = std::bit_cast;
#else
template<typename To, typename From>
#if __cplusplus >= 201703L
[[nodiscard]]
#endif
To bit_cast(From &&from)
#if __cplusplus >= 201402L
noexcept(std::is_nothrow_constructible<To>::value)
#endif
{
static_assert(std::is_trivially_copyable<typename std::remove_cv<typename std::remove_reference<From>::type>::type>::value, "From type must be trivially copable.");
static_assert(std::is_trivially_copyable<typename std::remove_cv<typename std::remove_reference<To>::type>::type>::value, "To type must be trivially copiable.");
static_assert(sizeof(From) == sizeof(To), "Sizes of From and To types must be the same.");
static_assert(std::is_default_constructible<To>::value, "To type must be default constructible.");
#if __cplusplus >= 201402L
auto result = (typename std::aligned_storage_t<sizeof(To), alignof(To)>){};
#else
To result{};
#endif
return *static_cast<To*>(memcpy(&result, &from, sizeof(To)));
}
#endif
namespace detail
{
template<typename Addr>
typename std::enable_if<std::is_integral<Addr>::value, void*>::type
any_to_voidp(Addr addr) { return reinterpret_cast<void*>(addr); }
template<typename Addr>
typename std::enable_if<!std::is_integral<Addr>::value, void*>::type
any_to_voidp(Addr addr) { return bit_cast<void*>(addr); }
}
#if _WIN32
/// Calls specified address as __stdcall function passing any amount of arguments. Return type is specified as first template argument. Available only on windows.
/// \param address of function to call.
/// \param ... any amount of arguments with any types.
/// \returns value of type specified as first template argument, or none if no type is specified.
template<typename Result, typename Addr, typename... Args>
Result stdcall(Addr address, Args... arguments)
{
typedef Result(__stdcall*UniversalCall)(Args...);
return UniversalCall(address)(arguments...);
}
# define HOOKER_CDECL __cdecl
#else
# define HOOKER_CDECL
#endif
/// Calls specified address as __cdecl function passing any amount of arguments. Return type is specified as first template argument.
/// \param address of function to call.
/// \param ... any amount of arguments with any types.
/// \returns value of type specified as first template argument, or none if no type is specified.
template<typename Result, typename Addr, typename... Args>
Result ccall(Addr address, Args... arguments)
{
typedef Result(HOOKER_CDECL*UniversalCall)(Args...);
return reinterpret_cast<UniversalCall>(address)(arguments...);
}
#undef HOOKER_CDECL
/// Calls specified address as __thiscall function with provided address as 'this' pointer, passing any amount of arguments. Return type is specified as first template argument.
/// \param address of function to call.
/// \param ... any amount of arguments with any types.
/// \returns value of type specified as first template argument, or none if no type is specified.
template<typename Result, typename Addr, typename This, typename... Args>
Result thiscall(Addr address, This thisPtr, Args... arguments)
{
detail::Helper* thisHelper = bit_cast<detail::Helper*>(thisPtr);
typedef Result(detail::Helper::*TUniversalCall)(Args...);
TUniversalCall UniversalCall = bit_cast<TUniversalCall>(address);
return (thisHelper->*UniversalCall)(arguments...);
}
/// Return object of specified `Type` which is located at `base + offset`.
template<typename Type, typename Base>
Type at_offset(Base base, unsigned offset)
{
return bit_cast<Type>(bit_cast<std::uintptr_t>(base) + offset);
}
/// Return object of specified `Type` which is located at `base + offset`.
template<typename Type, typename Base>
Type from_offset(Base base, unsigned offset)
{
return *at_offset<Type*>(base, offset);
}
/// Change protection of memory range.
/// \param p memory address.
/// \param size of memory at address p.
/// \param protection a combination of HOOKER_MEM_* flags.
/// \param original_protection on supported platforms will be set to current memory protection mode. May be null. If not null - always initialize to a best-guess current protection flags value, because on some platforms (like linux) this variable will not be set.
template<typename Type CPP14(=void*), typename Addr>
bool mem_protect(Addr p, size_t size, size_t protection, size_t* original_protection=nullptr) { return detail::hooker_mem_protect(detail::any_to_voidp(p), size, protection, original_protection) == HOOKER_SUCCESS; }
/// Get mnemonic size of current platform.
template<typename Addr>
size_t get_mnemonic_size(Addr address, size_t min_size) { return detail::hooker_get_mnemonic_size(detail::any_to_voidp(address), min_size); }
/// Hotpatch a call.
template<typename OriginalProc CPP14(=void*), typename Addr, typename ProcAddr>
OriginalProc hotpatch(Addr location, ProcAddr new_proc) { return bit_cast<OriginalProc>(detail::hooker_hotpatch(detail::any_to_voidp(location), detail::any_to_voidp(new_proc))); }
/// Unhotpatch a call.
template<typename Type CPP14(=void*), typename Addr>
bool unhotpatch(Addr location) { return detail::hooker_unhotpatch(detail::any_to_voidp(location)) == HOOKER_SUCCESS; }
/// Writes a jump or call hook from `address` to `new_proc`.
/// \param address a pointer where hook should be written
/// \param new_proc a pointer where hook should point to.
/// \param flags any of HOOKER_HOOK_* flags. They may not be combined.
/// \param nops of bytes to nop after hook instruction. Specify -1 to autocalculate.
/// \returns null on failure or non-null on success.
template<typename Addr, typename ProcAddr>
bool write_instruction(Addr address, ProcAddr new_proc, size_t flags, size_t nops=-1) { return detail::hooker_write_instruction(detail::any_to_voidp(address), detail::any_to_voidp(new_proc), flags, nops) == HOOKER_SUCCESS; }
/// Writes a 5 byte jump with E9 opcode. Difference between pointers is limited to 32bit.
/// \param address a pointer where hook should be written
/// \param new_proc a pointer where hook should point to.
/// \returns null on failure or non-null on success.
template<typename Addr, typename ProcAddr>
bool write_jmp(Addr address, ProcAddr new_proc) { return detail::hooker_write_jmp(detail::any_to_voidp(address), detail::any_to_voidp(new_proc)) == HOOKER_SUCCESS; }
/// Writes a 5 byte call with E8 opcode. Difference between pointers is limited to 32bit.
/// \param address a pointer where hook should be written
/// \param new_proc a pointer where hook should point to.
/// \returns null on failure or non-null on success.
template<typename Addr, typename ProcAddr>
bool write_call(Addr address, ProcAddr new_proc) { return detail::hooker_write_call(detail::any_to_voidp(address), detail::any_to_voidp(new_proc)) == HOOKER_SUCCESS; }
/// Redirect call to custom proc.
/// \param address a start of original call. Warning: It should not contain any relatively-addressed instructions like calls or jumps.
/// \param new_proc a proc that will be called instead of original one.
/// \returns pointer, calling which will invoke original proc. It is user's responsibility to call original code when necessary.
template<typename OriginalProc CPP14(=void*), typename Addr, typename ProcAddr>
OriginalProc hook(Addr address, ProcAddr new_proc, size_t flags=0) { return bit_cast<OriginalProc>(detail::hooker_hook(detail::any_to_voidp(address), detail::any_to_voidp(new_proc), flags)); }
/// Unhook a hook created by hooker::hook(.., .., HOOKER_HOOK_REDIRECT, ..).
/// \param address where hook was written to.
/// \param original result of hooker::redirect() call.
template<typename Addr, typename OriginalProc>
void unhook(Addr address, OriginalProc original) { detail::hooker_unhook(detail::any_to_voidp(address), detail::any_to_voidp(original)); }
/// Return address in object's vmt which is pointing to specified method.
/// \param object is a pointer to a c++ object.
/// \param method is a pointer to a c++ object method.
template<typename Proc, typename Addr, typename ProcAddr>
Proc& get_vmt_address(Addr object, ProcAddr method) { return *bit_cast<Proc*>(detail::hooker_get_vmt_address(reinterpret_cast<void*>(object), detail::any_to_voidp(method))); }
/// Find a first occurrence of memory pattern.
/// \param start a pointer to beginning of memory range.
/// \param size a size of memory range. If size is 0 then entire memory space will be searched. If pattern does not exist this will likely result in a crash. Negative size will search backwards.
/// \param pattern a array of bytes to search for.
/// \param pattern_len a length of pattern array.
/// \param a wildcard byte in the pattern array.
template<typename Type CPP14(=uint8_t*), typename Addr, typename Pattern>
Type find_pattern(Addr start, int size, const Pattern* pattern, size_t pattern_len, uint8_t wildcard) { return bit_cast<Type>(detail::hooker_find_pattern(detail::any_to_voidp(start), size, reinterpret_cast<const uint8_t*>(pattern), pattern_len, wildcard)); }
#if __cplusplus >= 201402L
/// Find a first occurrence of memory pattern.
/// \param start a pointer to beginning of memory range.
/// \param size a size of memory range. If size is 0 then entire memory space will be searched. If pattern does not exist this will likely result in a crash. Negative size will search backwards.
/// \param pattern and wildcard mask.
template<typename Type CPP14(=uint8_t*), typename Addr, size_t N>
Type find_pattern(Addr start, int size, const pattern<N>& pattern) { return bit_cast<Type>(detail::hooker_find_pattern_ex(detail::any_to_voidp(start), size, pattern.pattern, N, pattern.wildcard)); }
#endif
/// Find a first occurrence of string pattern.
/// \param start a pointer to beginning of memory range.
/// \param size a size of memory range. If size is 0 then entire memory space will be searched. If pattern does not exist this will likely result in a crash. Negative size will search backwards.
/// \param pattern a string to search.
/// \param wildcard a wildcard character.
template<typename Type CPP14(=char*), typename Addr>
Type find_pattern(Addr start, int size, const char* pattern, char wildcard='?') { return bit_cast<Type>(detail::hooker_find_pattern(detail::any_to_voidp(start), size, reinterpret_cast<const uint8_t*>(pattern), strlen(pattern), static_cast<uint8_t>(wildcard))); }
/// Fill memory with nops (0x90 opcode).
/// \param start of the memory address.
/// \param size of the memory that will be filled. Nops a single instruction when not specified.
/// \returns true on success or false on failure.
template<typename Addr>
bool nop(Addr start, size_t size=-1) { return detail::hooker_nop(detail::any_to_voidp(start), size) == HOOKER_SUCCESS; }
/// Write a value to specified memory address.
/// \param start of the memory address.
/// \param value to be written.
/// \returns true on success or false on failure.
template<typename Type, typename Addr>
bool write(Addr address, const Type value) { return detail::hooker_write(detail::any_to_voidp(address), (void*)&value, sizeof(value)) == HOOKER_SUCCESS; }
/// Write an array to specified memory address.
/// \param start of the memory address.
/// \param value to be written.
/// \param count of elements in the array.
template<typename Type, typename Addr>
bool write(Addr address, const Type* value, size_t count) { return detail::hooker_write(detail::any_to_voidp(address), (void*)value, sizeof(Type) * count) == HOOKER_SUCCESS; }
/// Write bytes to specified memory address.
/// \param start of the memory address.
/// \param data to be written.
/// \param size of data.
/// \returns true on success or false on failure.
template<typename Addr>
bool write(Addr address, const void* data, size_t size) { return detail::hooker_write(detail::any_to_voidp(address), data, size) == HOOKER_SUCCESS; }
/// Searches for symbol in specified library. On Windows LoadLibrary() will be called if its not loaded yet, otherwise GetModuleHandle() will be used.
/// On linux dlopen(RTLD_NODELETE) and dlclose() will always be called.
/// \param lib_name string with dynamic library name.
/// \param sym_name string with exported symbol name.
/// \returns pointer to resolved dynamic symbol.
template<typename Proc CPP14(=void*)>
Proc dlsym(const char* lib_name, const char* sym_name) { return bit_cast<Proc>(detail::hooker_dlsym(lib_name, sym_name)); }
#if _WIN32
/// Replaces entry in import address table of specified module.
/// \param mod_name string with name of module whose import table is to be modified.
/// \param imp_mod_name string with name of module which module specified in `mod_name` imports.
/// \param imp_proc_name string with name of symbol imported from module specified in `imp_mod_name`.
/// \param new_proc a pointer that should replace old entry in import address table.
/// \returns original value that was in import address table or null pointer on failure.
template<typename OrigProc CPP14(= void*), typename NewProc>
OrigProc hook_iat(const char* mod_name, const char* imp_mod_name, const char* imp_proc_name, NewProc new_proc) { return bit_cast<OrigProc>(detail::hooker_hook_iat(mod_name, imp_mod_name, imp_proc_name, detail::any_to_voidp(new_proc))); }
/// Replaces entry in import address table of specified module.
/// \param mod_name string with name of module whose import table is to be modified.
/// \param imp_mod_name string with name of module which module specified in `mod_name` imports.
/// \param imp_proc_name string with name of symbol imported from module specified in `imp_mod_name`.
/// \param new_proc a pointer that should replace old entry in import address table.
/// \param old_proc a pointer which will receive Can be null.
/// \returns true on success or false on failure.
template<typename OrigProc CPP14(=void*), typename NewProc>
bool hook_iat(const char* mod_name, const char* imp_mod_name, const char* imp_proc_name, NewProc new_proc, OrigProc* old_proc)
{
auto result = hook_iat<OrigProc>(mod_name, imp_mod_name, imp_proc_name, detail::any_to_voidp(new_proc));
if (old_proc)
*old_proc = result;
return result != HOOKER_ERROR;
}
#endif
};
#if HOOKER_USE_SHORT_NAMESPACE
# ifndef HOOKER_SHORT_NAMESPACE
# define HOOKER_SHORT_NAMESPACE hk
# endif
namespace HOOKER_SHORT_NAMESPACE = hooker;
#endif

31
CMakeLists.txt Normal file
View File

@@ -0,0 +1,31 @@
cmake_minimum_required(VERSION 3.26)
project(vsdbg-fix)
add_library(stub SHARED stub.cpp 3rdparty/hooker.c exports.def)
set(DST_PATH "C:/Users/Fragcolor2/.cursor/extensions/ms-vscode.cpptools-1.21.6-win32-x64/debugAdapters/vsdbg/bin")
# Set target name to vsdbg.dll
set_target_properties(stub PROPERTIES
OUTPUT_NAME vsdbg
)
target_compile_definitions(stub PUBLIC
_CRT_SECURE_NO_WARNINGS=1
HOOKER_X64=1
)
# Copy output dll to vsdbg directory
add_custom_command(
OUTPUT ${DST_PATH}/vsdbg.dll
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:stub> ${DST_PATH}
# Copy PDB too
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_PDB_FILE:stub> ${DST_PATH}
DEPENDS stub
COMMENT "Copying vsdbg.dll to ${DST_PATH}"
)
add_custom_target(copy_vsdbg ALL
DEPENDS ${DST_PATH}/vsdbg.dll
)

14
exports.def Normal file
View File

@@ -0,0 +1,14 @@
EXPORTS
InitializePAL = _0
WrapICorDebugHandleValue = _1
GetVilValueFieldOffset = _2
Initialize = _3
InitializeSession = _4
RunMainLoop = _5
ShutdownSession = _6
Shutdown = _7
DllGetClassObject = _8
GetUnixDomainSocketSizes = _9
AnalyzeSnapshot = _10
RunDiagnosticAnalysis = _11
OptionalHeader.AddressOfEntryPoint = _12

90
stub.cpp Normal file
View File

@@ -0,0 +1,90 @@
// Stubs for patching calls into vsdbg.1.dll
extern "C" {
void _0() { _0(); }
void _1() { _0(); }
void _2() { _0(); }
void _3() { _0(); }
void _4() { _0(); }
void _5() { _0(); }
void _6() { _0(); }
void _7() { _0(); }
void _8() { _0(); }
void _9() { _0(); }
void _10() { _0(); }
void _11() { _0(); }
void _12() { _0(); }
}
void (*stuff[])() = {
&_0, &_1, &_2, &_3, &_4, &_5, &_6, &_7, &_8, &_9, &_10, &_11, &_12,
};
const char *symlist[] = {
"InitializePAL",
"WrapICorDebugHandleValue",
"GetVilValueFieldOffset",
"Initialize",
"InitializeSession",
"RunMainLoop",
"ShutdownSession",
"Shutdown",
"DllGetClassObject",
"GetUnixDomainSocketSizes",
"AnalyzeSnapshot",
"RunDiagnosticAnalysis",
"OptionalHeader.AddressOfEntryPoint",
};
const char *origDll = "vsdbg.1.dll";
const char *logPath = "A:\\Projects\\vsdbg-fix\\log.txt";
#include "3rdparty/hooker.h"
#include <stdio.h>
#include <windows.h>
FILE *logFile;
void loadOrig() {
auto lib = LoadLibraryA(origDll);
for (int i = 0; i < sizeof(symlist) / sizeof(symlist[0]); i++) {
auto sym = GetProcAddress(lib, symlist[i]);
if (sym) {
if (!hooker_write_instruction(stuff[i], (void *)sym,
HOOKER_HOOK_FAT | HOOKER_HOOK_JMP, -1)) {
fprintf(logFile, "Failed to write jmp for %s\n", symlist[i]);
fflush(logFile);
} else {
fprintf(logFile, "Successfully wrote jmp for %s\n", symlist[i]);
fflush(logFile);
}
// fprintf(logFile, "Failed to hotpatch %s\n", symlist[i]);
// }
}
}
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call,
LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
logFile = fopen(logPath, "w");
while (!IsDebuggerPresent()) {
Sleep(100);
fprintf(logFile, "Waiting for debugger...\n");
fflush(logFile);
}
fprintf(logFile, "Running\n");
fflush(logFile);
loadOrig();
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

13
vsdbg.txt Normal file
View File

@@ -0,0 +1,13 @@
180000000+13a0
vsdbg.1.dll:$13a0
; Debugger init something
vsdbg.1:$6b2fa
; Obfus stuff
vsdbg.1:$0aecb0
; signatureCheck
vsdbg.1:$6afcc
vsdbg.1:$6b088