Generic System API Hooking, Parameter Logging, and Data Filtering with C++ Templates
This note shows how C++ templates and a one‑line macro can generate a universal wrapper that hooks any system API, logs and filters its parameters—including variadic and pointer arguments—while handling special cases such as const and void* buffers, eliminating repetitive custom wrappers.
In this note we demonstrate how to hook any system API with a single line of code, record its parameters, and filter or modify data, focusing on the sensitive data itself rather than specific API arguments.
Background : Most API‑hook libraries require a custom wrapper (e.g., MyTestFunc1) that mirrors the original function’s signature. This leads to repetitive code when many APIs need to be hooked.
Generalized handling logic : By leveraging C++ templates we can automatically generate a generic wrapper that works for any API. The AnyCall struct extracts the return type and argument types of the target function and provides a static FunctionCreater that forwards the arguments.
bool TestFunc1(char a, int b) { return a + b; }
bool MyTestFunc1(char& a, int& b) { return a + b; }
blackbone::Detour<decltype(&TestFunc1)> hook;
hook.Hook(&TestFunc1, &MyTestFunc1, blackbone::HookType::Inline);Writing a separate Myxxx function for each API quickly becomes cumbersome.
Template‑based solution : Define a primary template AnyCall and specialize it for function types. The specialization provides a static FunctionCreater that can log arguments and invoke the original API.
template<typename RET, typename... ARGS>
struct AnyCall;
template<typename RET, typename... ARGS>
struct AnyCall<RET(ARGS...)> {
static RET FunctionCreater(ARGS&... args) {
// do something ...
}
};Using a macro we can hook an API with one line:
#define HookApi(FuncName) \
static blackbone::Detour<decltype(&FuncName)> hook##FuncName; \
hook##FuncName.Hook(&FuncName, &AnyCall<decltype(FuncName)>::FunctionCreater, blackbone::HookType::Inline);
HookApi(ReadFile);
HookApi(WriteFile);
HookApi(CreateFile);Parameter name retrieval : Since the template only receives the function type, we embed the function name as a compile‑time string (C++17 static local string) and pass it to the macro.
#define HookApi(FuncName) \
static const wchar_t name##FuncName[] = L#FuncName; \
static blackbone::Detour<decltype(&FuncName)> hook##FuncName; \
hook##FuncName.Hook(&FuncName, &AnyCall<name##FuncName, decltype(FuncName)>::FunctionCreater, blackbone::HookType::Inline);
HookApi(ReadFile);Variadic‑argument logging : Expand the argument pack using an initializer list and stream each argument to std::wcout (or any logger).
template<typename RET, typename... ARGS>
struct AnyCall {
static RET FunctionCreater(ARGS&... args) {
std::initializer_list<int> expandLog{ (std::wcout << args << "|", 0)... };
}
};To handle types that cannot be streamed directly, we introduce a LogArgs helper that uses a concept to detect printable types and falls back to printing the type name.
template<typename T>
concept CANLOG_TYPE = requires(std::wstringstream &logInfo, T x) { logInfo << x; };
template<CANLOG_TYPE ArgType>
void LogArgs(std::wstringstream &logInfo, ArgType &&arg) {
if constexpr (std::is_pointer_v<std::decay_t<ArgType>> && !arg) {
logInfo << "nullptr|";
return;
}
logInfo << arg << "|";
}
template<typename ArgType>
void LogArgs(std::wstringstream &logInfo, ArgType &&arg) {
logInfo << typeid(ArgType).name() << "|";
}Special parameter handling :
Const‑qualified parameters are ignored (no modification needed).
Pointer parameters are dereferenced recursively; for non‑pointer data we obtain the size via sizeof and process the raw bytes.
Void* parameters (e.g., DeviceIoControl buffers) require a manual association between a buffer and its size, which can be expressed using compile‑time string comparison or tuple indexing.
template<const wchar_t* funcName, typename RET, typename... ARGS>
struct AnyCall;
template<const wchar_t* funcName, typename RET, typename... ARGS>
struct AnyCall<funcName, RET(ARGS...)> {
static RET FunctionCreater(ARGS&... args) {
if constexpr (std::is_same_v<decltype(DeviceIoControl), RET(ARGS...)>) {
RelationArgHandler<4,5>(args...); // link buffer and size
}
// generic logging and handling
std::initializer_list<int> expandLog{ (LogArgs(logInfo, args), 0)... };
if constexpr (!std::is_void_v<RET>) return RET{};
}
};Assembly view : A simplified test case shows the generated assembly simply streams each argument and calls ArgHandler for further processing.
bool TestFunc1(int a, int* b, int** c) { return a + b; }
auto func = AnyCall<decltype(TestFunc1)>::FunctionCreater;
int a = 1; int* b = nullptr; int** c = nullptr;
func(a, b, c);The output is 1|0000000000000000|0000000000000000|int|int|int|, confirming correct logging.
Summary :
One‑line system API hooking.
Parameter recording and data filtering.
Focus on sensitive data rather than specific API arguments.
Generic handling logic applicable to most APIs, with special‑case extensions for const, pointer, and void* parameters.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Tencent Cloud Developer
Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
