mirror of
https://github.com/acidicoala/SmokeAPI.git
synced 2025-12-05 21:15:39 -05:00
Refactored function selector hooking
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
#define VIRTUAL(TYPE) __declspec(noinline) TYPE __fastcall
|
||||
|
||||
#define GET_ORIGINAL_HOOKED_FUNCTION(FUNC) \
|
||||
const auto FUNC##_o = koalabox::hook::get_original_hooked_function(globals::address_map, #FUNC, FUNC);
|
||||
static const auto FUNC##_o = koalabox::hook::get_original_hooked_function(globals::address_map, #FUNC, FUNC);
|
||||
|
||||
#define GET_ORIGINAL_FUNCTION_STEAMAPI(FUNC) \
|
||||
static const auto FUNC##_o = koalabox::hook::get_original_function(globals::steamapi_module, #FUNC, FUNC);
|
||||
|
||||
@@ -7,56 +7,54 @@
|
||||
namespace koalageddon {
|
||||
using namespace koalabox;
|
||||
|
||||
// Offset values are interpreted according to pointer arithmetic rules, i.e.
|
||||
// 1 unit offset represents 4 and 8 bytes in 32-bit and 64-bit architectures respectively.
|
||||
struct KoalageddonConfig {
|
||||
// Offset values are interpreted according to pointer arithmetic rules, i.e.
|
||||
// 1 unit offset represents 4 and 8 bytes in 32-bit and 64-bit architectures respectively.
|
||||
uint32_t vstdlib_callback_interceptor_address_offset = 1;
|
||||
uint32_t vstdlib_callback_address_offset = 20;
|
||||
uint32_t vstdlib_callback_data_offset = 0;
|
||||
uint32_t vstdlib_callback_name_offset = 4;
|
||||
uint32_t IClientAppManager_IsAppDlcInstalled_ordinal = 8;
|
||||
uint32_t IClientApps_BGetDLCDataByIndex_ordinal = 9;
|
||||
uint32_t IClientApps_GetDLCCount_ordinal = 8;
|
||||
uint32_t IClientInventory_CheckResultSteamID_ordinal = 5;
|
||||
uint32_t IClientInventory_GetAllItems_ordinal = 8;
|
||||
uint32_t IClientInventory_GetItemDefinitionIDs_ordinal = 19;
|
||||
uint32_t IClientInventory_GetItemsByID_ordinal = 9;
|
||||
uint32_t IClientInventory_GetResultItemProperty_ordinal = 3;
|
||||
uint32_t IClientInventory_GetResultItems_ordinal = 2;
|
||||
uint32_t IClientInventory_GetResultStatus_ordinal = 0;
|
||||
uint32_t IClientInventory_SerializeResult_ordinal = 6;
|
||||
uint32_t IClientUser_BIsSubscribedApp_ordinal = 191;
|
||||
|
||||
uint32_t client_engine_steam_client_internal_ordinal = 12;
|
||||
uint32_t steam_client_internal_interface_selector_ordinal = 18;
|
||||
String steamclient_interface_interceptor_pattern = "55 8B EC 8B ?? ?? ?? ?? ?? 81 EC ?? ?? ?? ?? 53 FF 15";
|
||||
|
||||
uint32_t IClientAppManager_IsAppDlcInstalled_ordinal = 8;
|
||||
uint32_t IClientApps_GetDLCCount_ordinal = 8;
|
||||
uint32_t IClientApps_BGetDLCDataByIndex_ordinal = 9;
|
||||
uint32_t IClientInventory_GetResultStatus_ordinal = 0;
|
||||
uint32_t IClientInventory_GetResultItems_ordinal = 2;
|
||||
uint32_t IClientInventory_GetResultItemProperty_ordinal = 3;
|
||||
uint32_t IClientInventory_CheckResultSteamID_ordinal = 5;
|
||||
uint32_t IClientInventory_GetAllItems_ordinal = 8;
|
||||
uint32_t IClientInventory_GetItemsByID_ordinal = 9;
|
||||
uint32_t IClientInventory_SerializeResult_ordinal = 6;
|
||||
uint32_t IClientInventory_GetItemDefinitionIDs_ordinal = 19;
|
||||
uint32_t IClientUser_BIsSubscribedApp_ordinal = 191;
|
||||
uint32_t vstdlib_callback_address_offset = 20;
|
||||
uint32_t vstdlib_callback_data_offset = 0;
|
||||
uint32_t vstdlib_callback_interceptor_address_offset = 1;
|
||||
uint32_t vstdlib_callback_name_offset = 4;
|
||||
|
||||
// We do not use *_WITH_DEFAULT macro to ensure that overriding
|
||||
// the koalageddon config requires definition of all keys
|
||||
NLOHMANN_DEFINE_TYPE_INTRUSIVE(
|
||||
KoalageddonConfig, // NOLINT(misc-const-correctness)
|
||||
vstdlib_callback_interceptor_address_offset,
|
||||
vstdlib_callback_address_offset,
|
||||
vstdlib_callback_data_offset,
|
||||
vstdlib_callback_name_offset,
|
||||
IClientAppManager_IsAppDlcInstalled_ordinal,
|
||||
IClientApps_BGetDLCDataByIndex_ordinal,
|
||||
IClientApps_GetDLCCount_ordinal,
|
||||
IClientInventory_CheckResultSteamID_ordinal,
|
||||
IClientInventory_GetAllItems_ordinal,
|
||||
IClientInventory_GetItemDefinitionIDs_ordinal,
|
||||
IClientInventory_GetItemsByID_ordinal,
|
||||
IClientInventory_GetResultItemProperty_ordinal,
|
||||
IClientInventory_GetResultItems_ordinal,
|
||||
IClientInventory_GetResultStatus_ordinal,
|
||||
IClientInventory_SerializeResult_ordinal,
|
||||
IClientUser_BIsSubscribedApp_ordinal,
|
||||
|
||||
client_engine_steam_client_internal_ordinal,
|
||||
steam_client_internal_interface_selector_ordinal,
|
||||
steamclient_interface_interceptor_pattern,
|
||||
|
||||
IClientAppManager_IsAppDlcInstalled_ordinal,
|
||||
IClientApps_GetDLCCount_ordinal,
|
||||
IClientApps_BGetDLCDataByIndex_ordinal,
|
||||
IClientInventory_GetResultStatus_ordinal,
|
||||
IClientInventory_GetResultItems_ordinal,
|
||||
IClientInventory_GetResultItemProperty_ordinal,
|
||||
IClientInventory_CheckResultSteamID_ordinal,
|
||||
IClientInventory_GetAllItems_ordinal,
|
||||
IClientInventory_GetItemsByID_ordinal,
|
||||
IClientInventory_SerializeResult_ordinal,
|
||||
IClientInventory_GetItemDefinitionIDs_ordinal,
|
||||
IClientUser_BIsSubscribedApp_ordinal
|
||||
vstdlib_callback_address_offset,
|
||||
vstdlib_callback_data_offset,
|
||||
vstdlib_callback_interceptor_address_offset,
|
||||
vstdlib_callback_name_offset
|
||||
)
|
||||
};
|
||||
|
||||
|
||||
@@ -7,120 +7,88 @@
|
||||
#include <Zydis/Zydis.h>
|
||||
#include <Zydis/DecoderTypes.h>
|
||||
|
||||
using namespace koalabox;
|
||||
|
||||
#define SELECTOR_DECL(INTERFACE) \
|
||||
constexpr auto INTERFACE = #INTERFACE; \
|
||||
DLL_EXPORT(void) INTERFACE##_Selector(const void*, const void*, const void*, const void*); \
|
||||
|
||||
SELECTOR_DECL(IClientAppManager)
|
||||
SELECTOR_DECL(IClientApps)
|
||||
SELECTOR_DECL(IClientInventory)
|
||||
SELECTOR_DECL(IClientUser)
|
||||
|
||||
DLL_EXPORT(void) SteamClient_Interface_Interceptor(const char* interface_name, const char* function_name);
|
||||
|
||||
namespace koalageddon {
|
||||
// Maps interface name to interface pointer
|
||||
Map<String, const void*> interface_name_pointer_map{}; // NOLINT(cert-err58-cpp)
|
||||
std::mutex map_mutex;
|
||||
|
||||
Set<String> hooked_interfaces; // NOLINT(cert-err58-cpp)
|
||||
using namespace koalabox;
|
||||
|
||||
ZydisDecoder decoder = {};
|
||||
|
||||
constexpr auto INTERFACE_TARGET_COUNT = 4;
|
||||
#define HOOK_FUNCTION(FUNC) hook::swap_virtual_func_or_throw( \
|
||||
globals::address_map, \
|
||||
interface, \
|
||||
#FUNC, \
|
||||
koalageddon::config.FUNC##_ordinal, \
|
||||
(FunctionAddress) FUNC \
|
||||
);
|
||||
|
||||
/*
|
||||
* We need an interface=>function ordinal map in order to hook steam interface function
|
||||
* via virtual function pointer swap. We could construct it by exploiting these code chunks:
|
||||
*
|
||||
* 8B01 | mov eax, dword ptr ds:[ecx]
|
||||
* 68 ???????? | push steamclient.function_name
|
||||
* 68 ???????? | push steamclient.interface_name
|
||||
*
|
||||
* Step 1: Find all addresses that begin with pattern
|
||||
* 8B 01 68 ?? ?? ?? ?? 68 ?? ?? ?? ?? FF 10
|
||||
* Step 2: Extract function and interface name pointers by adding 3 and 8 respectively
|
||||
* Step 3: Starting from the found address, and until the function epilogue, scan for either instructions:
|
||||
*
|
||||
* (1) FF50 ?? | call dword ptr ds:[eax+0x??]
|
||||
* or
|
||||
* (2) 8B40 ?? | mov eax, dword ptr ds:[eax+??]
|
||||
* FFD0 | call eax
|
||||
*
|
||||
* In the case (1), the offset is encoded in the found call instruction.
|
||||
* In the case (2), the offset is encoded in the instruction preceding the call.
|
||||
*
|
||||
* ROADBLOCK: There is actually a case (3) which calls a local variable (ebp-??),
|
||||
* which itself is constructed over multiple instruction calls, making it non-deterministic.
|
||||
* Until this roadblock is resolved, automatic detection of ordinals remains non-viable.
|
||||
*/
|
||||
[[maybe_unused]] Map<String, uint32_t> construct_interface_function_ordinal_map() {
|
||||
auto* const steamclient_handle = win_util::get_module_handle_or_throw(STEAMCLIENT_DLL);
|
||||
const auto steamclient_module_info = win_util::get_module_info_or_throw(steamclient_handle);
|
||||
DLL_EXPORT(void) IClientAppManager_Selector(
|
||||
const void* interface,
|
||||
const void* arg2,
|
||||
const void* arg3,
|
||||
const void* arg4
|
||||
) {
|
||||
static std::once_flag flag;
|
||||
std::call_once(flag, [&]() {
|
||||
HOOK_FUNCTION(IClientAppManager_IsAppDlcInstalled)
|
||||
});
|
||||
|
||||
auto* byte_pointer = (uint8_t*) steamclient_module_info.lpBaseOfDll;
|
||||
while (byte_pointer) {
|
||||
// Search until the end of DLL section
|
||||
auto rva = (DWORD) byte_pointer - (DWORD) steamclient_module_info.lpBaseOfDll;
|
||||
|
||||
// This pattern needs to be parameterized if this method ever gets implemented.
|
||||
const String interface_function_chunk_pattern = "8B 01 68 ?? ?? ?? ?? 68";
|
||||
|
||||
byte_pointer = reinterpret_cast<uint8_t*>(
|
||||
patcher::find_pattern_address(
|
||||
byte_pointer,
|
||||
steamclient_module_info.SizeOfImage - rva,
|
||||
"interface=>function chunk",
|
||||
interface_function_chunk_pattern,
|
||||
false
|
||||
)
|
||||
);
|
||||
|
||||
// const auto* interface_name = *reinterpret_cast<char**>(byte_pointer + 3);
|
||||
// const auto* function_name = *reinterpret_cast<char**>(byte_pointer + 8);
|
||||
|
||||
byte_pointer += interface_function_chunk_pattern.size();
|
||||
byte_pointer += interface_function_chunk_pattern.size();
|
||||
|
||||
static const auto is_epilogue = [](const uint8_t* instruction_byte) {
|
||||
const uint8_t epilogue[]{
|
||||
0x8B, 0xE5, // mov esp,ebp
|
||||
0x5D, // pop ebp
|
||||
0xC3, // ret
|
||||
};
|
||||
|
||||
for (auto i = 0; i < sizeof(epilogue); i++) {
|
||||
if (epilogue[i] != instruction_byte[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
static const auto is_call_dword = [](const uint8_t* instruction_byte) {
|
||||
return instruction_byte[0] == 0xFF && instruction_byte[1] == 0x50;
|
||||
};
|
||||
|
||||
// static const auto is_call_eax = [](const uint8_t* instruction_byte) {
|
||||
// return instruction_byte[0] == 0x8B && instruction_byte[1] == 0x40 &&
|
||||
// instruction_byte[3] == 0xFF && instruction_byte[4] == 0xD0;
|
||||
// };
|
||||
|
||||
while (!is_epilogue(byte_pointer)) {
|
||||
if (is_call_dword(byte_pointer)) {
|
||||
// Find a way to determine offset
|
||||
}
|
||||
|
||||
byte_pointer++;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
GET_ORIGINAL_HOOKED_FUNCTION(IClientAppManager_Selector)
|
||||
IClientAppManager_Selector_o(interface, arg2, arg3, arg4);
|
||||
}
|
||||
|
||||
DLL_EXPORT(void) IClientApps_Selector(
|
||||
const void* interface,
|
||||
const void* arg2,
|
||||
const void* arg3,
|
||||
const void* arg4
|
||||
) {
|
||||
static std::once_flag flag;
|
||||
std::call_once(flag, [&]() {
|
||||
HOOK_FUNCTION(IClientApps_GetDLCCount)
|
||||
HOOK_FUNCTION(IClientApps_BGetDLCDataByIndex)
|
||||
});
|
||||
|
||||
GET_ORIGINAL_HOOKED_FUNCTION(IClientApps_Selector)
|
||||
IClientApps_Selector_o(interface, arg2, arg3, arg4);
|
||||
}
|
||||
|
||||
DLL_EXPORT(void) IClientInventory_Selector(
|
||||
const void* interface,
|
||||
const void* arg2,
|
||||
const void* arg3,
|
||||
const void* arg4
|
||||
) {
|
||||
static std::once_flag flag;
|
||||
std::call_once(flag, [&]() {
|
||||
HOOK_FUNCTION(IClientInventory_GetResultStatus)
|
||||
HOOK_FUNCTION(IClientInventory_GetResultItems)
|
||||
HOOK_FUNCTION(IClientInventory_GetResultItemProperty)
|
||||
HOOK_FUNCTION(IClientInventory_CheckResultSteamID)
|
||||
HOOK_FUNCTION(IClientInventory_GetAllItems)
|
||||
HOOK_FUNCTION(IClientInventory_GetItemsByID)
|
||||
HOOK_FUNCTION(IClientInventory_SerializeResult)
|
||||
HOOK_FUNCTION(IClientInventory_GetItemDefinitionIDs)
|
||||
});
|
||||
|
||||
GET_ORIGINAL_HOOKED_FUNCTION(IClientInventory_Selector)
|
||||
IClientInventory_Selector_o(interface, arg2, arg3, arg4);
|
||||
}
|
||||
|
||||
DLL_EXPORT(void) IClientUser_Selector(
|
||||
const void* interface,
|
||||
const void* arg2,
|
||||
const void* arg3,
|
||||
const void* arg4
|
||||
) {
|
||||
static std::once_flag flag;
|
||||
std::call_once(flag, [&]() {
|
||||
HOOK_FUNCTION(IClientUser_BIsSubscribedApp)
|
||||
});
|
||||
|
||||
GET_ORIGINAL_HOOKED_FUNCTION(IClientUser_Selector)
|
||||
IClientUser_Selector_o(interface, arg2, arg3, arg4);
|
||||
}
|
||||
|
||||
|
||||
FunctionAddress get_absolute_address(ZydisDecodedInstruction instruction, FunctionAddress address) {
|
||||
const auto op = instruction.operands[0];
|
||||
|
||||
@@ -202,13 +170,13 @@ namespace koalageddon {
|
||||
|
||||
// Finally, hook the selector functions of interest
|
||||
|
||||
if (IClientAppManager == interface_name) {
|
||||
if ("IClientAppManager" == interface_name) {
|
||||
DETOUR_ADDRESS(IClientAppManager_Selector, function_selector_address)
|
||||
} else if (IClientApps == interface_name) {
|
||||
} else if ("IClientApps" == interface_name) {
|
||||
DETOUR_ADDRESS(IClientApps_Selector, function_selector_address)
|
||||
} else if (IClientInventory == interface_name) {
|
||||
} else if ("IClientInventory" == interface_name) {
|
||||
DETOUR_ADDRESS(IClientInventory_Selector, function_selector_address)
|
||||
} else if (IClientUser == interface_name) {
|
||||
} else if ("IClientUser" == interface_name) {
|
||||
DETOUR_ADDRESS(IClientUser_Selector, function_selector_address)
|
||||
}
|
||||
|
||||
@@ -235,111 +203,5 @@ namespace koalageddon {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto interface_interceptor_address = (FunctionAddress) patcher::find_pattern_address(
|
||||
module_info,
|
||||
"SteamClient_Interface_Interceptor",
|
||||
config.steamclient_interface_interceptor_pattern
|
||||
);
|
||||
|
||||
if (interface_interceptor_address) {
|
||||
DETOUR_ADDRESS(SteamClient_Interface_Interceptor, interface_interceptor_address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function intercepts interface name and function names, which we need to determine which functions to hook.
|
||||
* Unfortunately we can't reliably get interface pointer in this function, hence we need to hook corresponding
|
||||
* parent selector functions which will contain the interface pointer as the first parameter.
|
||||
*/
|
||||
DLL_EXPORT(void) SteamClient_Interface_Interceptor(const char* interface_name, const char* function_name) {
|
||||
try {
|
||||
const std::lock_guard<std::mutex> guard(koalageddon::map_mutex);
|
||||
|
||||
const auto needs_hooking = koalageddon::hooked_interfaces.size() < koalageddon::INTERFACE_TARGET_COUNT;
|
||||
const auto has_interface_pointer = koalageddon::interface_name_pointer_map.contains(interface_name);
|
||||
|
||||
// logger->trace(
|
||||
// "Intercepted interface function: '{}::{}'",
|
||||
// interface_name, function_name
|
||||
// );
|
||||
|
||||
if (needs_hooking && has_interface_pointer) {
|
||||
const auto* interface_address = koalageddon::interface_name_pointer_map[interface_name];
|
||||
|
||||
const auto hook_if_needed = [&](const String& name, const std::function<void()>& block) {
|
||||
const auto is_target_interface = interface_name == name;
|
||||
const auto is_hooked = koalageddon::hooked_interfaces.contains(name);
|
||||
const auto is_valid_address = interface_address != nullptr;
|
||||
|
||||
if (is_target_interface && !is_hooked && is_valid_address) {
|
||||
block();
|
||||
koalageddon::hooked_interfaces.insert(name);
|
||||
}
|
||||
};
|
||||
|
||||
#define HOOK_INTERFACE(FUNC) hook::swap_virtual_func_or_throw( \
|
||||
globals::address_map, \
|
||||
(void*) interface_address, \
|
||||
#FUNC, \
|
||||
koalageddon::config.FUNC##_ordinal, \
|
||||
(FunctionAddress) FUNC \
|
||||
);
|
||||
|
||||
hook_if_needed(IClientAppManager, [&]() {
|
||||
HOOK_INTERFACE(IClientAppManager_IsAppDlcInstalled)
|
||||
});
|
||||
|
||||
hook_if_needed(IClientApps, [&]() {
|
||||
HOOK_INTERFACE(IClientApps_GetDLCCount)
|
||||
HOOK_INTERFACE(IClientApps_BGetDLCDataByIndex)
|
||||
});
|
||||
|
||||
hook_if_needed(IClientInventory, [&]() {
|
||||
HOOK_INTERFACE(IClientInventory_GetResultStatus)
|
||||
HOOK_INTERFACE(IClientInventory_GetResultItems)
|
||||
HOOK_INTERFACE(IClientInventory_GetResultItemProperty)
|
||||
HOOK_INTERFACE(IClientInventory_CheckResultSteamID)
|
||||
HOOK_INTERFACE(IClientInventory_GetAllItems)
|
||||
HOOK_INTERFACE(IClientInventory_GetItemsByID)
|
||||
HOOK_INTERFACE(IClientInventory_SerializeResult)
|
||||
HOOK_INTERFACE(IClientInventory_GetItemDefinitionIDs)
|
||||
});
|
||||
|
||||
hook_if_needed(IClientUser, [&]() {
|
||||
HOOK_INTERFACE(IClientUser_BIsSubscribedApp)
|
||||
});
|
||||
}
|
||||
|
||||
GET_ORIGINAL_HOOKED_FUNCTION(SteamClient_Interface_Interceptor)
|
||||
SteamClient_Interface_Interceptor_o(interface_name, function_name);
|
||||
} catch (const Exception& ex) {
|
||||
logger->error("{} -> Error: {}", __func__, ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This macro will generate a definition of a selector function,
|
||||
* which will cache the interface pointer in the local map.
|
||||
*/
|
||||
#define SELECTOR_IMPL(INTERFACE) \
|
||||
DLL_EXPORT(void) INTERFACE##_Selector( \
|
||||
const void* arg1, \
|
||||
const void* arg2, \
|
||||
const void* arg3, \
|
||||
const void* arg4 \
|
||||
) { \
|
||||
if(!koalageddon::hooked_interfaces.contains(INTERFACE)){ \
|
||||
const std::lock_guard<std::mutex> guard(koalageddon::map_mutex); \
|
||||
koalageddon::interface_name_pointer_map[INTERFACE] = arg1; \
|
||||
} \
|
||||
GET_ORIGINAL_HOOKED_FUNCTION(INTERFACE##_Selector) \
|
||||
INTERFACE##_Selector_o(arg1, arg2, arg3, arg4); \
|
||||
}
|
||||
|
||||
SELECTOR_IMPL(IClientAppManager)
|
||||
SELECTOR_IMPL(IClientApps)
|
||||
SELECTOR_IMPL(IClientInventory)
|
||||
SELECTOR_IMPL(IClientUser)
|
||||
|
||||
Reference in New Issue
Block a user