Smx
Smx

Reputation: 61

How can i override a global symbol in a library loaded with dlopen?

There are 3 components involved

How can i have other.so use the overridden puts from loader.so?

Note that i want puts to be overridden only from loader.so onwards (included other.so), the main program should be unaffected

Sample code follows

main.c

#include <stdio.h>
#include <dlfcn.h>

int main(int argc, char *argv[]){
    dlopen("./loader.so", RTLD_NOW | RTLD_GLOBAL | RTLD_DEEPBIND);
    puts("Normal");
    return 0;
}

loader.c

#include <stdio.h>
#include <dlfcn.h>

extern int puts(const char *s){
    fputs("Hooked: ", stdout);
    fputs(s, stdout);
    fputc('\n', stdout);
    return 0;
}

__attribute__((constructor))
void ctor(void) {
    puts("Something");
    void *other = dlopen("./other.so", RTLD_NOW);
}

other.c

#include <stdio.h>

__attribute__((constructor))
void ctor(void) {
    puts("Hello!");
}

make.sh

#!/bin/bash
gcc main.c -o main -ldl
gcc loader.c -fPIC -shared -Wl,-Bsymbolic -o loader.so
gcc other.c -fPIC -shared -o other.so

Desired output

Hooked: Something
Hooked: Hello!
Normal

Actual output

Hooked: Something
Hello!
Normal

Upvotes: 4

Views: 1150

Answers (2)

Smx
Smx

Reputation: 61

After playing with the problem a bit more, i have a solution that requires a bit of external help from patchelf, so i'll wait to accept this solution in case there's a different approach to the problem.

This solution works by making a new shared object, shared.so, with the modified puts, as following

int puts(const char *s){
    fputs("Hooked: ", stdout);
    fputs(s, stdout);
    fputc('\n', stdout);
    return 0;
}

We then need to force other.so to depend on this new shared object, and we can do this by using patchelf --add-needed shared.so other.so

This does involve a modification to other.so, but it doesn't require a re-compilation from source (which makes this approach more feasible).

Now, when we load other.so, we need to specify RTLD_DEEPBIND inside loader.c like this

void *other = dlopen("./libother.so", RTLD_NOW | RTLD_GLOBAL | RTLD_DEEPBIND);

so that the search order won't start from the global context, but from the library itself. Since other.so doesn't define puts, the direct dependencies will be looked up, and puts will be found in shared.so

The properties of RTLD_DEEPBIND make sure that even eventual LD_PRELOADed objects are trumped over.

So if puts is disabled inside a preloaded shared object we can work around that and call the real, unmodified puts from glibc (and only for calls originating from other.so).

We don't need any patchelf or shared.so if all we want is restore the original behaviour

Upvotes: 1

Grzegorz Żur
Grzegorz Żur

Reputation: 49181

Try adding flag -Wl,--no-as-needed

gcc loader.c -fPIC -shared -Wl,-Bsymbolic -Wl,--no-as-needed -o loader.so

I successfully hooked time related functions from C library in time-machine.

Upvotes: 0

Related Questions