Date: Mon, 29 Jul 2024 01:23:29 +0200
Hi,
On Sun, Jul 28, 2024 at 6:47 PM Frederick Virchanza Gotham via
Std-Proposals <std-proposals_at_[hidden]> wrote:
> I just realised today that the "interceptor function" doesn't
> necessarily need to end with a jump to the original function.
[...]
> In our MITM DLL file, when either "OpenSocket" or "CloseSocket" is
called, we want to lock a mutex, then call the original function, then
> unlock the mutex, then return. This scenario is a little more
> complicated than what I described in my original post because the
> "interceptor function" won't end with a jump to the original function
> (instead it will end with a jump back to the caller).
>
[...]
> In C++26, if we were to have "interceptor functions", then the above
> assembler would become:
>
> std::mutex m;
>
> auto OpenSocket(auto) interceptor
> {
> WriteLog( "Function called 'OpenSocket'");
> m.lock();
> auto const h = LoadLibraryA("transport.dll.original");
> auto const pf = (void(*)(void))GetProcAddress(h, "OpenSocket");
> goto pf;
> m.unlock();
> goto return;
> }
>
> I'm 99% certain that this technique will work on every CPU with every
> calling convention. Even when supernumerary arguments are pushed onto
> the stack, still the last thing pushed onto the stack is the return
> address.
This only works as long as your locals (ASDVs) don't interfere. In fact, if
you were to use std::lock_guard<std::mutex> there, it would probably be
overwritten on the stack. This is because, on most architectures,
parameters and locals (ASDVs) use the same stack. Now, you *might* say this
should work:
std::mutex m;
auto OpenSocket(auto&&... args) /* interceptor */
{
WriteLog( "Function called 'OpenSocket'");
std::lock_guard<std::mutex> guard(m);
auto const h = LoadLibraryA("transport.dll.original");
auto const pf = (void(*)(void))GetProcAddress(h, "OpenSocket");
return pf(std::forward<decltype(args)>(args)...);
}
You simply need to ask for this to go to the DLL, without any further
instantiations. That's a change, that might or might not be doable - note
that, due to locals, there's a stack frame of the interceptor that exists
in a stack-based implementation *while* calling pf but *before* returning
from the interceptor.
But note, this needs moves (or any other kind of forwarding, which might or
might not be detectable in the code of the DLL being loaded). If you don't
want locals of the interceptor to interfere, then you can't have locals
(that cannot be overwritten), and that's a serious limitation.
Also, it's not apparent whether the goto pf in this mail is supposed to
mean the same thing as in the original mail or not. Originally, you
expected it to *replace* the current function; now, you expect it like a
call, where we can return to.
Thanks,
-lorro
On Sun, Jul 28, 2024 at 6:47 PM Frederick Virchanza Gotham via
Std-Proposals <std-proposals_at_[hidden]> wrote:
> I just realised today that the "interceptor function" doesn't
> necessarily need to end with a jump to the original function.
[...]
> In our MITM DLL file, when either "OpenSocket" or "CloseSocket" is
called, we want to lock a mutex, then call the original function, then
> unlock the mutex, then return. This scenario is a little more
> complicated than what I described in my original post because the
> "interceptor function" won't end with a jump to the original function
> (instead it will end with a jump back to the caller).
>
[...]
> In C++26, if we were to have "interceptor functions", then the above
> assembler would become:
>
> std::mutex m;
>
> auto OpenSocket(auto) interceptor
> {
> WriteLog( "Function called 'OpenSocket'");
> m.lock();
> auto const h = LoadLibraryA("transport.dll.original");
> auto const pf = (void(*)(void))GetProcAddress(h, "OpenSocket");
> goto pf;
> m.unlock();
> goto return;
> }
>
> I'm 99% certain that this technique will work on every CPU with every
> calling convention. Even when supernumerary arguments are pushed onto
> the stack, still the last thing pushed onto the stack is the return
> address.
This only works as long as your locals (ASDVs) don't interfere. In fact, if
you were to use std::lock_guard<std::mutex> there, it would probably be
overwritten on the stack. This is because, on most architectures,
parameters and locals (ASDVs) use the same stack. Now, you *might* say this
should work:
std::mutex m;
auto OpenSocket(auto&&... args) /* interceptor */
{
WriteLog( "Function called 'OpenSocket'");
std::lock_guard<std::mutex> guard(m);
auto const h = LoadLibraryA("transport.dll.original");
auto const pf = (void(*)(void))GetProcAddress(h, "OpenSocket");
return pf(std::forward<decltype(args)>(args)...);
}
You simply need to ask for this to go to the DLL, without any further
instantiations. That's a change, that might or might not be doable - note
that, due to locals, there's a stack frame of the interceptor that exists
in a stack-based implementation *while* calling pf but *before* returning
from the interceptor.
But note, this needs moves (or any other kind of forwarding, which might or
might not be detectable in the code of the DLL being loaded). If you don't
want locals of the interceptor to interfere, then you can't have locals
(that cannot be overwritten), and that's a serious limitation.
Also, it's not apparent whether the goto pf in this mail is supposed to
mean the same thing as in the original mail or not. Originally, you
expected it to *replace* the current function; now, you expect it like a
call, where we can return to.
Thanks,
-lorro
Received on 2024-07-28 23:23:42