kubo / plthook

Hook function calls by replacing PLT(Procedure Linkage Table) entries.
762 stars 156 forks source link

A fatal error about the handle is invalid #35

Closed Mr-JingShi closed 3 years ago

Mr-JingShi commented 3 years ago

https://github.com/kubo/plthook/blob/85dad5081b5ebdbeeea790aeef95eb60b3748dea/plthook_elf.c#L374

I am working on 64-bit ubuntu20.04. I was learning ELF and hook recently, so I found pltkook. plthook is so cool,plthook is a very useful tool, I learned how to hook a dynamic library function, thanks.

but I found a fatal error, I think ... lmap is equal to hndl, so, if hndl is invalid after dlclose, lmap is invalid too.

I add memset into here:

if (dlinfo(hndl, RTLD_DI_LINKMAP, &lmap) != 0) {
        set_errmsg("dlinfo error");
        dlclose(hndl);
        return PLTHOOK_FILE_NOT_FOUND;
    }
    dlclose(hndl);
    memset(hndl, 0x00, 16+1);
    return plthook_open_real(plthook_out, lmap);

lmap's l_addr and l_ld are invalid,then the program crashed

how about adding the hndl into struct plthook:

struct plthook {
    void* hndl;
    const Elf_Sym *dynsym;
    const char *dynstr;
    size_t dynstr_size;
    const char *plt_addr_base;
    const Elf_Plt_Rel *rela_plt;
    size_t rela_plt_cnt;
#ifdef R_GLOBAL_DATA
    const Elf_Plt_Rel *rela_dyn;
    size_t rela_dyn_cnt;
#endif
};

Can you reply me? thanks

Mr-JingShi commented 3 years ago

Sorry,I thought I was wrong when I submit the issue, so I closed it, but after debugging, I found that I was right, so I reopened it.

Mr-JingShi commented 3 years ago

https://github.com/kubo/plthook/blob/85dad5081b5ebdbeeea790aeef95eb60b3748dea/plthook_elf.c#L279 https://github.com/kubo/plthook/blob/85dad5081b5ebdbeeea790aeef95eb60b3748dea/plthook_elf.c#L305 https://github.com/kubo/plthook/blob/85dad5081b5ebdbeeea790aeef95eb60b3748dea/plthook_elf.c#L369

We should hold the hndl (the return value of dlopen) until pltook_close.

Can you reply me?

kubo commented 3 years ago

lmap points to a node of a linked list maintained by the dynamic linker. On the other hand, POSIX doesn't define actual type of a handle returned by dlopen.

I think ... lmap is equal to hndl,

It is correct on Linux. However it isn't on other platforms using ELF. Linux glibc chooses to reuse lmap as a dlopen handle.

The following code prints same two addresses on Linux.

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

int main()
{
  struct link_map *lmap = _r_debug.r_map; // See link.h
  while (lmap != NULL) {
    if (strstr(lmap->l_name, "libc.so.6") != NULL) {
      printf("%p: link_map address of libc.so.6 searched from the global variable _r_debug\n", lmap);
    }
    lmap = lmap->l_next;
  }
  printf("%p: dlopen handle of libc.so.6\n", dlopen("libc.so.6", RTLD_LAZY));
}

so, if hndl is invalid after dlclose, lmap is invalid too.

No. lmap is valid until the dynamic library is unloaded from the process.

Mr-JingShi commented 3 years ago

Thanks for talking with me

test.c

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

void test() {
  struct link_map *lmap = _r_debug.r_map; // See link.h
  while (lmap != NULL) {
    if (strstr(lmap->l_name, "libtest.so") != NULL) {
      printf("%p: link_map address of libtest.so searched from the global variable _r_debug\n", lmap);
    } else if (strstr(lmap->l_name, "libc.so.6") != NULL) {
      printf("%p: link_map address of libc.so.6 searched from the global variable _r_debug\n", lmap);
    }
    lmap = lmap->l_next;
  }
}

int main()
{
  printf("-------------------\n");
  test();
  printf("-------------------\n");

  void* handle = dlopen("libtest.so", RTLD_LAZY);
  if (handle != NULL) {
    printf("%p: dlopen handle of libtest.so\n", handle);
  }

  printf("-------------------\n");
  test();
  dlclose(handle);

  printf("-------------------\n");
  test();
  printf("-------------------\n");
  return 0;
}
gcc -o test test.c -ldl

ldd test
  linux-vdso.so.1 (0x00007ffe6d3fa000)
  libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f72cc1fa000)
  libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f72cbe09000)
  /lib64/ld-linux-x86-64.so.2 (0x00007f72cc600000)

./test
-------------------
0x7f18128ec4f0: link_map address of libc.so.6 searched from the global variable _r_debug
-------------------
0x55b015f49690: dlopen handle of libtest.so
-------------------
0x7f18128ec4f0: link_map address of libc.so.6 searched from the global variable _r_debug
0x55b015f49690: link_map address of libtest.so searched from the global variable _r_debug
-------------------
0x7f18128ec4f0: link_map address of libc.so.6 searched from the global variable _r_debug
-------------------

Sometimes dynamic libraries are used as plugins, if dlopen is used without linking libtest.so (such as my test.c/test), it is not ok. your's testprog is ok, because you have linked libtest.so.

kubo commented 3 years ago

In https://man7.org/linux/man-pages/man3/dlclose.3.html,

The function dlclose() decrements the reference count on the dynamically loaded shared object referred to by handle.

If the object's reference count drops to zero and no symbols in this object are required by other objects, then the object is unloaded after first calling any destructors defined for the object.

https://github.com/kubo/plthook/blob/85dad5081b5ebdbeeea790aeef95eb60b3748dea/plthook_elf.c#L374

This code doesn't unload the shared library even when it is loaded by dlopen.

if dlopen is used without linking libtest.so (such as my test.c/test), it is not ok.

In this situation, the reference count of libtest.so increases to two at line 353 and decreases to one at line 374. The count doesn't drop to zero.

Mr-JingShi commented 3 years ago

Thank you,I see

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

#include <plthook.h>

static double strtod_cdecl_hook_func(const char *str, char **endptr)
{
    printf("strtod_cdecl_hook_func\n");

    return 0;
}

int main()
{
  plthook_t* plthook = NULL;
  const char* filename = "libtest.so";
  plthook_open(&plthook, filename);

  unsigned int pos = 0;
  const char* name = NULL;
  void **addr = NULL;
  const char* funcname = "strtod_cdecl";
  unsigned int found = 0;
  while (plthook_enum(plthook, &pos, &name, &addr) == 0) {
    if (strcmp(funcname, name) == 0) {
        found = 1;
        break;
    }
  }
  if (0 != found)
  {
    plthook_replace(plthook, funcname, (void*)strtod_cdecl_hook_func, NULL);
  }

  plthook_close(plthook);

  // strtod_cdecl("3.7", NULL);

  return 0;
}
gcc -o test test.c ../plthook_elf.c -ldl

My previous idea was not to link libtest.so when compiling, such as above. But i found i was wrong. (1) I can not call strtod_cdecl whitout linking libtest.so, Unless I explicitly call the dlopen and dlsym. (2) https://man7.org/linux/man-pages/man3/dlclose.3.html, RTLD_NOLOAD (since glibc 2.2) Don't load the shared object. This can be used to test if the object is already resident (dlopen() returns NULL if it is not, or the object's handle if it is resident). This flag can also be used to promote the flags on a shared object that is already loaded. For example, a shared object that was previously loaded with RTLD_LOCAL can be reopened with RTLD_NOLOAD | RTLD_GLOBAL. https://github.com/kubo/plthook/blob/85dad5081b5ebdbeeea790aeef95eb60b3748dea/plthook_elf.c#L353 If I do not link libtest.so, Line 353 hndl is NULL. (3) if i don't link libtest.so and don't call strtod_cdecl in main, strtod_cdecl doesn't appear in Dynamic Relocation Symbol Table. It's not PLT than delayed binding.

I see, Thank you