Information Security 15 min read

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.

Tencent Cloud Developer
Tencent Cloud Developer
Tencent Cloud Developer
Generic System API Hooking, Parameter Logging, and Data Filtering with C++ Templates

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
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
struct AnyCall;

template
struct AnyCall
{
    static RET FunctionCreater(ARGS&... args) {
        // do something ...
    }
};

Using a macro we can hook an API with one line:

#define HookApi(FuncName) \
    static blackbone::Detour
hook##FuncName; \
    hook##FuncName.Hook(&FuncName, &AnyCall
::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
hook##FuncName; \
    hook##FuncName.Hook(&FuncName, &AnyCall
::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
struct AnyCall {
    static RET FunctionCreater(ARGS&... args) {
        std::initializer_list
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
concept CANLOG_TYPE = requires(std::wstringstream &logInfo, T x) { logInfo << x; };

template
void LogArgs(std::wstringstream &logInfo, ArgType &&arg) {
    if constexpr (std::is_pointer_v
> && !arg) {
        logInfo << "nullptr|";
        return;
    }
    logInfo << arg << "|";
}

template
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
struct AnyCall;

template
struct AnyCall
{
    static RET FunctionCreater(ARGS&... args) {
        if constexpr (std::is_same_v
) {
            RelationArgHandler<4,5>(args...); // link buffer and size
        }
        // generic logging and handling
        std::initializer_list
expandLog{ (LogArgs(logInfo, args), 0)... };
        if constexpr (!std::is_void_v
) 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
::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.

CsecurityTemplatesAPI HookingData FilteringParameter Logging
Tencent Cloud Developer
Written by

Tencent Cloud Developer

Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.