C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Interceptor Function (preserve stack and all registers)

From: Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>
Date: Tue, 30 Jul 2024 00:36:53 +0100
On Mon, Jul 29, 2024 at 9:49 PM Thiago Macieira wrote:
>
>
> You missed my point of reentrancy: what happens if this function is reentered
> on the same thread while it's already running?
>
> Why would it do that? Well, why wouldn't it do that? You want to contemplate
> this as a Standard feature, so it needs to contemplate the use-case when the
> function is recursive.
>
> int OpenSocket(int af, std::string_view address)
> {
> if (af == AF_INET)
> return OpenSocket(AF_INET6, std::string("::ffff:") + address);
> return OpenSocket_inner(address);
> }


I am loath to be a one trick pony and so I've switched operating
system and also switched compiler.

Here's your recursive interceptor function running on MS-Windows
x86_32 with the VC++ compiler:

    https://godbolt.org/z/f3rfhdshx

And here it is copy-pasted:

#include <cassert> // assert
#include <cstdint> // uintptr_t
#include <iostream> // cout, endl
#include <mutex> // recursive_mutex

extern "C" void* __stdcall LoadLibraryA(char const*);
extern "C" void* __stdcall GetProcAddress(void*, char const*);

// ===============================================
// To change the library name or function name,
// only edit the following two lines:
#define MACRO_FILE kernel32.dll
#define MACRO_FUNC EnumResourceTypesA
// -----------------------------------------------
// Don't edit the next five lines:
#define Q(x) #x
#define QUOTE(x) Q(x)
extern "C" void MACRO_FUNC(void);
constexpr char str_func[] = QUOTE(MACRO_FUNC);
constexpr char str_file[] = QUOTE(MACRO_FILE);
// ===============================================

std::recursive_mutex m;

// A thread_local variable is used to store
// the original return address because we
// can neither use a caller-saved nor a
// callee-saved register for this purpose
constexpr unsigned max_recursion = 16u;
thread_local unsigned count_recursion = -1;
thread_local void (*original_return_address[max_recursion])(void);

// The "Lock" function does 4 things:
// (1) Lock the mutex
// (2) Store the original return address
// inside a thread_local variable
// (3) Log the calling of the function
// (4) Return the address of the original
// function
extern "C" auto __cdecl Inwards( void (*const arg)(void) ) -> void(*)(void)
{
    ++count_recursion;

    m.lock();
    std::cout << "Mutex is locked\n";

    assert( count_recursion < max_recursion );
    original_return_address[count_recursion] = arg;

    std::cout << "Function called: '" << str_func << " (recursion
depth = " << count_recursion << ")\n";

    auto const h = LoadLibraryA(str_file);
    assert( nullptr != h );
    auto const pf = GetProcAddress(h, str_func);
    assert( nullptr != pf );

    return (void(*)(void))pf;
}

// The "Unlock" function does 3 things:
// (1) Unlock the mutex
// (2) Log the return of the original function
// (3) Return the original return address
extern "C" auto __cdecl Outwards(void) -> void(*)(void)
{
    std::cout << "Function '" << str_func << "' has returned from
recursive call " << count_recursion << "\n";

    m.unlock();
    std::cout << "Mutex has been unlocked\n";

    return original_return_address[count_recursion--];
}

void __declspec(naked) MACRO_FUNC(void)
{
    __asm {
        mov eax, [esp] // save original return address

        pushf
        push ebp
        push ebx
        push edi
        push esi

        push eax // set 1st argument = return address
        call Inwards
        add esp, 4 // remove 1st argument from stack

        pop esi
        pop edi
        pop ebx
        pop ebp
        popf

        add esp, 4 // remove return address from top of stack
        lea edx, come_back_here
        push edx // set the new return address at top of stack
        jmp eax // jump to original function

     come_back_here:
        pushf
        push ebp
        push ebx
        push edi
        push esi

        call Outwards

        pop esi
        pop edi
        pop ebx
        pop ebp
        popf

        jmp eax // jump back to caller
    }
}

// ======================================================
// ======================================================
// ======================================================
// ======================================================
// Now let's test it out:

int __stdcall MyCallback( void *const h, char const *const s, void
(*const arg)(void) )
{
    if ( 0u == ((uintptr_t)s & 0xFFFF0000) )
    {
        if ( LoadLibraryA("user32.dll") != h )
        {
            auto const pf = (int(__stdcall*)(void*, void*, void*))arg;
            pf( LoadLibraryA("user32.dll"), MyCallback, &EnumResourceTypesA );
        }

        return 0;
    }

    if ( s && *s ) std::cout << "Resource Type: " << s << std::endl;

    return 1;
}

int main(void)
{
    std::cout << "First line in main.\n";

    auto const pf = (int(__stdcall*)(void*, void*, void*))&EnumResourceTypesA;
    pf( LoadLibraryA("shell32.dll"), MyCallback, &EnumResourceTypesA );

    std::cout << "Last line in main.\n";
}

Received on 2024-07-29 23:37:02