This is called child-gating and frida has a very good example
Here is a demo with a simple application. A simple C program with a fork
and we try to hook puts
for both child and parent.
(test3) [frida-example] cat test.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
puts("before");
pid_t pid = fork();
if (pid) {
puts("parent");
} else {
puts("child");
}
}
(test3) [frida-example] make test
cc test.c -o test
With the example - adding changes
def _start(self):
argv = ["./test"]
print("✔ spawn(argv={})".format(argv))
pid = self._device.spawn(argv, env={}, stdio='pipe')
self._instrument(pid)
and
def _instrument(self, pid):
print("✔ attach(pid={})".format(pid))
session = self._device.attach(pid)
session.on("detached", lambda reason: self._reactor.schedule(lambda: self._on_detached(pid, session, reason)))
print("✔ enable_child_gating()")
session.enable_child_gating()
print("✔ create_script()")
script = session.create_script("""\
Interceptor.attach(Module.getExportByName(null, 'puts'), {
onEnter: function (args) {
send({
type: 'puts',
path: Memory.readUtf8String(args[0])
});
}
});
""")
then by running
(test3) [frida-example] python hook.py
✔ spawn(argv=['./test'])
✔ attach(pid=1968)
...
✔ resume(pid=1968)
⚡ message: pid=1968, payload={'type': 'puts', 'path': 'before'}
⚡ message: pid=1968, payload={'type': 'puts', 'path': 'parent'}
...
⚡ child_added: Child(pid=1977, parent_pid=1968, origin=fork)
✔ attach(pid=1977)
...
✔ resume(pid=1977)
⚡ child_removed: Child(pid=1977, parent_pid=1968, origin=fork)
⚡ message: pid=1977, payload={'type': 'puts', 'path': 'child'}
...
⚡ detached: pid=1977, reason='process-terminated'
We hooked both the instances of child and parent
findModuleByName
can only find libraries loaded into the current process, therefore it will never find level2.exe unless it is not started/executed but loaded as library into the current process. I don't see a different way than hooking the seconds process using an external Python script.