MetaCall PLT/IAT Hooking Proof of Concept
May 9, 2025 ยท View on GitHub
Proof of Concept for implementing PLT/IAT hooking for MetaCall. This will be used in order to allow MetaCall to be loaded in places where a runtime is already started.
The feature once implemented will solve the following isuses:
- https://github.com/metacall/core/issues/231
- https://github.com/metacall/core/issues/460
- https://github.com/metacall/core/issues/493
- https://github.com/metacall/core/issues/31
How it works
This PoC is based on a modified version of PLTHook Library from @kubo.
First of all we have the following preconditions:
libmetacallwhich loadslibnode_loader.libnode_loaderis not linked to anything but we are going to weakly link it tolibnode, this means that in Windows it must be linked with/DELAYLOAD, in Linux and MacOS it must not be linked.
There are two possible cases, this happens before loading libnode_loader:
-
MetaCall is not being executed by
node.exe, then:-
Windows:
libmetacallloads dynamically all the dependencies oflibnode_loader(akalibnode).- We list all the symbols of each dependency (aka
libnode) so we construct a hashmap of symbol string to symbol function pointer. - We list all the unresolved symbols of
libnode_loaderand we link them tolibnode.
-
MacOS & Linux:
libmetacallloads dynamically all the dependencies oflibnode_loader(akalibnode).- Linking is resolved by the linker automatically.
-
-
MetaCall is being executed by node.exe, then we have two possible cases:
-
node.execompiled statically (withoutlibnode):- We get all the library dependencies from
node.exeand we do not findlibnode, so we get the handle of the currrent process. - We list all symbols of
node.exeand we construct a hash map a hashmap of symbol string to symbol function pointer. - We list all the unresolved symbols of
libnode_loaderand we link them tonode.exe.
- We get all the library dependencies from
-
node.execompiled dynamically (withlibnode):- We get all the library dependencies from
node.exeand we findlibnodeso we get the handle from it. - We list all the symbols of each dependency (aka
libnode) so we construct a hashmap of symbol string to symbol function pointer of those dependencies (libnode). - We list all the unresolved symbols of
libnode_loaderand we link them tolibnodeofnode.exe.
- We get all the library dependencies from
-
Outcome
With this methodology we prevent loading a library that contains a runtime. This is very dangerous because numerous runtimes rely on constructors (C++ constructors of static class delacarations or C compiler dependant constructor mechanisms like GNU or Clang __attribute__((constructor))) that are mutually exclusive between them. So if we only load the library but we do not call method of the library, it can still cause errors.
The loaders will be redirected to the proper runtime, reusing the functions and instance of the already running runtime.
Features
- Works for Linux, Windows and MacOS with most of the architectures of each platform: https://github.com/metacall/plthook?tab=readme-ov-file#supported-platforms
- Hooks the functions and prevents runtime instances to be initialized, so it's fully transparent and has no side effects on the runtimes.
Limitations
- Currently it does not support
-O3on Linux with GCC compiler, neither/O2and/Ob2in Windows with MSVC. It works in MacOS with-O3and Clang.