wirteup ig
This commit is contained in:
parent
8dc14dcb5f
commit
dc9fa36d69
74
README.md
74
README.md
|
@ -3,9 +3,9 @@
|
||||||
Define dynamic shared objects and resolvable symbols at runtime, without
|
Define dynamic shared objects and resolvable symbols at runtime, without
|
||||||
creating an ELF file anywhere or touching the filesystem.
|
creating an ELF file anywhere or touching the filesystem.
|
||||||
|
|
||||||
It works by manipulating `ld.so`'s internal data structures. (That is, if it
|
It also only works on glibc, it will explode in your face if you try to run it
|
||||||
works at all). It also only works on glibc, it will explode in your face if you
|
with eg. musl. Additionally, your glibc binaries must *not* be stripped of
|
||||||
try to run it with eg. musl.
|
their symbol tables!
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ Other than that, here's an example:
|
||||||
```c
|
```c
|
||||||
// create a library
|
// create a library
|
||||||
struct dynso_lib* l;
|
struct dynso_lib* l;
|
||||||
dynso_create(&l, 0,
|
dynso_create(&l, 0, /* base address of the library - you can keep this at 0 */
|
||||||
(char*)"this is just a display name", "libtest", /* latter is the soname */
|
(char*)"this is just a display name", "libtest", /* latter is the soname */
|
||||||
NULL, LM_ID_BASE /* from dlfcn.h, you need to define _GNU_SOURCE first! */);
|
NULL, LM_ID_BASE /* from dlfcn.h, you need to define _GNU_SOURCE first! */);
|
||||||
|
|
||||||
|
@ -44,6 +44,15 @@ somefunc();
|
||||||
dynso_remove(l);
|
dynso_remove(l);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Example output:
|
||||||
|
|
||||||
|
```
|
||||||
|
dlsym("testsym") = 0x694201337
|
||||||
|
dlsym("testfunction") = 0x5589a1437d75
|
||||||
|
calling the resolved function:
|
||||||
|
hello world!
|
||||||
|
```
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
glibc, seems to work with 2.30.
|
glibc, seems to work with 2.30.
|
||||||
|
@ -56,7 +65,62 @@ make
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
no
|
Don't.
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
Basically, it works by manipulating `ld.so`'s internal data structures. (That
|
||||||
|
is, if it works at all).
|
||||||
|
|
||||||
|
glibc's `ld.so` internally keeps track of all DSOs using a thing
|
||||||
|
called a `link_map`, which is essentially a linked list of loaded DSOs. It is
|
||||||
|
documented in your system's `link.h` header file, and can be accessed from
|
||||||
|
`_r_debug.r_map`. (`dlopen` also returns `link_map`s, cast to a void pointer.)
|
||||||
|
|
||||||
|
However, that header file is lying to you. Internally, glibc adds *lots* of
|
||||||
|
stuff to this struct, as you can witness in `include/link.h` in the glibc
|
||||||
|
source code repository. With this knowledge, we can readily manipulate a lot
|
||||||
|
of things in order to have it do what we want.
|
||||||
|
|
||||||
|
Alright, that's great, but how do you create a new DSO without using
|
||||||
|
`dlopen`? For that, you can look at how `ld.so` adds the
|
||||||
|
[vDSO](https://lwn.net/Articles/446528/) to the `link_map` chain: it calls
|
||||||
|
`_dl_new_object`, an internal function. This one returns a new `link_map`
|
||||||
|
object, which is then initialized, and then aded to the global `link_map` chain
|
||||||
|
by calling `_dl_add_to_namespace_list`. Additionally, a call to
|
||||||
|
`_dl_setup_hash` seems to be needed to keep symbol resolution code happy.
|
||||||
|
|
||||||
|
So, how do you get to those functions? They aren't exported: you won't be
|
||||||
|
finding them in `ld.so`'s `.dynsym` section, which is the section containing
|
||||||
|
all exported symbols. However, when unstripped, `ld.so` also has a *second*
|
||||||
|
symbol table, `.symtab`. This one does contain a number of internal symbols in
|
||||||
|
its list, including the ones we need!
|
||||||
|
|
||||||
|
Now that we can instantiate new `link_map` objects, how do we add symbols to
|
||||||
|
them to make them resolvable? This is where the 'hidden' part of a `link_map`
|
||||||
|
comes into play: when glibc tries to resolve a symbol (`elf/dl-lookup.c`),
|
||||||
|
it will do a lookup in a hash table which maps symbol names of a single DSO to
|
||||||
|
their entries in the symbol table. Two lookup algorithms are used: one made by
|
||||||
|
the GNU people, and a legacy one invented for SysV. On first sight the former
|
||||||
|
looked more complcated to get to work correctly, so I opted for the latter.
|
||||||
|
|
||||||
|
The SysV algorithm calculates the symbol name's hash modulo some value (which
|
||||||
|
is taken from a field in the `link_map`), which it then uses to index another
|
||||||
|
table (`l_chain`), from where it reads an index into the actual symbol table.
|
||||||
|
From that point on, it starts walking the symbol table linearly until it finds
|
||||||
|
a matching symbol.
|
||||||
|
|
||||||
|
If you've written some code that accomplishes the above, you'll notice that
|
||||||
|
lookups with `dlsym()` will still return nothing. `ld.so`, instead of just
|
||||||
|
checking *all* DSOs for a given symbol, as this will not work with how symbols
|
||||||
|
are supposed to work in the ELF ABI: each DSO has a separate 'scope' of other
|
||||||
|
DSOs it can access symbols of, some symbols have certain visibility parameters
|
||||||
|
set (global, internal, hidden, and protected -- especially the latter one
|
||||||
|
requires this approach based on scopes), and other complicating factors.
|
||||||
|
|
||||||
|
However, a DSO's scope is also accessible from this very same `link_map`
|
||||||
|
struct, so we can just inject ourselves whenever that's needed. After doing
|
||||||
|
that, `dlsym()` works!
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue