3
votes

My objective is to hook the open function that dlopen on linux uses. For some reason, this code is not hooking dlopen->open, but it does hook my version of open main.c->open. Is dlopen not using my symbols somehow?

Compilation process is as follows:

  1. gcc main.c -ldl -ggdb
  2. gcc fake-open.c -o libexample.so -fPIC -shared
  3. export LD_PRELOAD="$PWD/libexample.so"

When I run the program, everything works. Ensuring the LD_PRELOAD variable is set.. etc.

Here is the problem, when I try to hook the open function directly or indirectly called by dlopen, somehow this "version" of open is not being resolved/redirected/hooked by my version.

[main.c]
#include <dlfcn.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
    puts("calling open");
    int fd = open("/tmp/test.so", O_RDONLY|O_CLOEXEC);

    puts("calling dlopen");
    int *handle = dlopen("/tmp/test.so", RTLD_LAZY);
}


[fake-open.c]
#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>
#include <sys/types.h>
#include <sys/stat.h>
//#include <fcntl.h>

int open(const char *pathname, int flags)
{
    puts("from hooked..");

    return 1;
}

Console Output:

calling open

from hooked..

calling dlopen


I know for a fact dlopen is somehow calling open due to strace.

write(1, "calling open\n", 13calling open
)          = 13
write(1, "from hooked..\n", 14from hooked..
)         = 14
write(1, "calling dlopen\n", 15calling dlopen
)        = 15
brk(0)                                  = 0x804b000
brk(0x806c000)                          = 0x806c000
open("/tmp/test.so", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\2\0\3\0\1\0\0\0`\205\4\0104\0\0\0"..., 512) = 512

But, for some reason, when dlopen calls open, it is not using my version of open. This has to be some kind of linking of run time symbol resolution problem, or perhaps dlopen is using a static version of open and doesnt need to resolve any symbols at run or load time?

1
Interesting question. I don't know the right answer at the moment, but thinking about it - if dynamic linker would need to resolve open to resolve any symbols, wouldn't this end up in endless loop?Erki Aring

1 Answers

4
votes

First, contrary to @usr's answer, dlopen does open the library.

We can confirm this by running a simple test under GDB:

// main.c
#include <dlfcn.h>
int main()
{
   void *h = dlopen("./foo.so", RTLD_LAZY);
   return 0;
}

// foo.c; compile with "gcc -fPIC -shared -o foo.so foo.c"
int foo() { return 0; }

Let's compile and run this:

gdb -q ./a.out
(gdb) start
Temporary breakpoint 1 at 0x400605: file main.c, line 4.
Starting program: /tmp/a.out

Temporary breakpoint 1, main () at main.c:4
4          void *h = dlopen("./foo.so", RTLD_LAZY);
(gdb) catch syscall open
Catchpoint 2 (syscall 'open' [2])
(gdb) c
Continuing.

Catchpoint 2 (call to syscall open), 0x00007ffff7df3497 in open64 () at ../sysdeps/unix/syscall-template.S:81
81  ../sysdeps/unix/syscall-template.S: No such file or directory.
(gdb) bt
#0  0x00007ffff7df3497 in open64 () at ../sysdeps/unix/syscall-template.S:81
#1  0x00007ffff7ddf5bd in open_verify (name=0x602010 "./foo.so", fbp=0x7fffffffd568, loader=<optimized out>, whatcode=<optimized out>, found_other_class=0x7fffffffd550, free_name=<optimized out>) at dl-load.c:1930
#2  0x00007ffff7de2d6f in _dl_map_object (loader=loader@entry=0x7ffff7ffe1c8, name=name@entry=0x4006a4 "./foo.so", type=type@entry=2, trace_mode=trace_mode@entry=0, mode=mode@entry=-1879048191, nsid=0) at dl-load.c:2543
#3  0x00007ffff7deea14 in dl_open_worker (a=a@entry=0x7fffffffdae8) at dl-open.c:235
#4  0x00007ffff7de9fc4 in _dl_catch_error (objname=objname@entry=0x7fffffffdad8, errstring=errstring@entry=0x7fffffffdae0, mallocedp=mallocedp@entry=0x7fffffffdad0, operate=operate@entry=0x7ffff7dee960 <dl_open_worker>, args=args@entry=0x7fffffffdae8) at dl-error.c:187
#5  0x00007ffff7dee37b in _dl_open (file=0x4006a4 "./foo.so", mode=-2147483647, caller_dlopen=<optimized out>, nsid=-2, argc=1, argv=0x7fffffffde28, env=0x7fffffffde38) at dl-open.c:661
#6  0x00007ffff7bd702b in dlopen_doit (a=a@entry=0x7fffffffdd00) at dlopen.c:66
#7  0x00007ffff7de9fc4 in _dl_catch_error (objname=0x7ffff7dd9110 <last_result+16>, errstring=0x7ffff7dd9118 <last_result+24>, mallocedp=0x7ffff7dd9108 <last_result+8>, operate=0x7ffff7bd6fd0 <dlopen_doit>, args=0x7fffffffdd00) at dl-error.c:187
#8  0x00007ffff7bd762d in _dlerror_run (operate=operate@entry=0x7ffff7bd6fd0 <dlopen_doit>, args=args@entry=0x7fffffffdd00) at dlerror.c:163
#9  0x00007ffff7bd70c1 in __dlopen (file=<optimized out>, mode=<optimized out>) at dlopen.c:87
#10 0x0000000000400614 in main () at main.c:4

This tells you that on 64-bit system, dlopen calls open64 instead of open, so your interposer wouldn't work (you'd need to interpose open64 instead).

But you are on a 32-bit system (as evidenced by the 0x806c000 etc. addresses printed by strace), and there the stack trace looks like this:

#0  0xf7ff3774 in open () at ../sysdeps/unix/syscall-template.S:81
#1  0xf7fe1211 in open_verify (name=0x804b008 "./foo.so", fbp=fbp@entry=0xffffc93c, loader=0xf7ffd938, whatcode=whatcode@entry=0, found_other_class=found_other_class@entry=0xffffc933, free_name=free_name@entry=true) at dl-load.c:1930
#2  0xf7fe4114 in _dl_map_object (loader=loader@entry=0xf7ffd938, name=name@entry=0x8048590 "./foo.so", type=type@entry=2, trace_mode=trace_mode@entry=0, mode=mode@entry=-1879048191, nsid=0) at dl-load.c:2543
#3  0xf7feec14 in dl_open_worker (a=0xffffccdc) at dl-open.c:235
#4  0xf7feac06 in _dl_catch_error (objname=objname@entry=0xffffccd4, errstring=errstring@entry=0xffffccd8, mallocedp=mallocedp@entry=0xffffccd3, operate=operate@entry=0xf7feeb50 <dl_open_worker>, args=args@entry=0xffffccdc) at dl-error.c:187
#5  0xf7fee644 in _dl_open (file=0x8048590 "./foo.so", mode=-2147483647, caller_dlopen=0x80484ea <main+29>, nsid=<optimized out>, argc=1, argv=0xffffcf74, env=0xffffcf7c) at dl-open.c:661
#6  0xf7fafcbc in dlopen_doit (a=0xffffce90) at dlopen.c:66
#7  0xf7feac06 in _dl_catch_error (objname=0xf7fb3070 <last_result+12>, errstring=0xf7fb3074 <last_result+16>, mallocedp=0xf7fb306c <last_result+8>, operate=0xf7fafc30 <dlopen_doit>, args=0xffffce90) at dl-error.c:187
#8  0xf7fb037c in _dlerror_run (operate=operate@entry=0xf7fafc30 <dlopen_doit>, args=args@entry=0xffffce90) at dlerror.c:163
#9  0xf7fafd71 in __dlopen (file=0x8048590 "./foo.so", mode=1) at dlopen.c:87
#10 0x080484ea in main () at main.c:4

So why isn't open_verifys call to open redirected to your open interposer?

First, let's look at the actual call instruction in frame 1:

(gdb) fr 1
#1  0xf7fe1211 in open_verify (name=0x804b008 "./foo.so", fbp=fbp@entry=0xffffc93c, loader=0xf7ffd938, whatcode=whatcode@entry=0, found_other_class=found_other_class@entry=0xffffc933, free_name=free_name@entry=true) at dl-load.c:1930
1930    dl-load.c: No such file or directory.
(gdb) x/i $pc-5
   0xf7fe120c <open_verify+60>: call   0xf7ff3760 <open>

Compare this to the call instruction in frame 10:

(gdb) fr 10
#10 0x080484ea in main () at main.c:4
4          void *h = dlopen("./foo.so", RTLD_LAZY);
(gdb) x/i $pc-5
   0x80484e5 <main+24>: call   0x80483c0 <dlopen@plt>

Notice anything different?

That's right: the call from main goes through the procedure linkage table (PLT), which the dynamic loader (ld-linux.so.2) resolves to appropriate definition.

But the call to open in frame 1 does not go through PLT (and thus is not interposable).

Why is that? Because that call must work before there is any other definition of open available -- it is used while the libc.so.6 (which normally supplies the definition of open) is itself being loaded (by the dynamic loader).

For this reason, the dynamic loader must be entirely self-contained (in fact in contains a statically linked in copy of a subset of libc).

My objective is to hook the open function that dlopen on linux uses.

For the reason above, this objective can't be achieved via LD_PRELOAD. You'll need to use some other hooking mechanism, such as patching the loader executable code at runtime.