Select Page
  1. Introduction
  2. Finding link_map
  3. Finding _dl_runtime_resolve()
  4. Conclusion


1. Introduction

When partial RELRO is enabled, the pointers to link_map and _dl_runtime_resolve() can be found in GOT[1] and GOT[2], correspondingly:

peilin@PWN:~/expr/dl_resolve$ gcc -Wl,-z,lazy -o partial simple.c
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : ENABLED
RELRO : Partial
gdb-peda$ elfheader .got.plt
.got.plt: 0x555555755000 – 0x555555755020 (data)
gdb-peda$ x/3gx 0x555555755000
0x555555755000: 0x0000000000200df8 0x00007ffff7ffe170
0x555555755010: 0x00007ffff7dec680

But when full RELRO is enabled, these two values are no longer initialized:

peilin@PWN:~/expr/dl_resolve$ gcc -Wl,-z,now -o full simple.c
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : ENABLED
RELRO : FULL
gdb-peda$ elfheader .got
.got: 0x555555754fb8 – 0x555555755000 (data)
gdb-peda$ x/3gx 0x555555754fb8
0x555555754fb8: 0x0000000000200dc8 0x0000000000000000
0x555555754fc8: 0x0000000000000000

So how can we find link_map and _dl_runtime_resolve() if we want to perform ret2dl-resolve, then? 🙁


2. Finding link_map

There is an entry inside .dynamic called DEBUG, used by debuggers. It contains a pointer to a rendezvous data structure called r_debug used by the dynamic loader, defined as below:

struct r_debug
  {
    int r_version;		     /* Version number for this protocol.  */

    struct link_map *r_map;	/* Head of the chain of loaded objects.  */

    /* This is the address of a function internal to the run-time linker,
       that will always be called when the linker begins to map in a
       library or unmap it, and again when the mapping change is complete.
       The debugger can set a breakpoint at this address if it wants to
       notice shared object mapping changes.  */
    ElfW(Addr) r_brk;
    enum
      {
	/* This state value describes the mapping change taking place when
	   the `r_brk' address is called.  */
	RT_CONSISTENT,		  /* Mapping change is complete.  */
	RT_ADD,			     /* Beginning to add a new object.  */
	RT_DELETE		       /* Beginning to remove an object mapping.  */
      } r_state;

    ElfW(Addr) r_ldbase;	/* Base address the linker is loaded at.  */
  };

As we can see, it actually contains a pointer to link_map in r_map field! Let’s see:

gdb-peda$ elfheader .dynamic
.dynamic: 0x555555754dc8 – 0x555555754fb8 (data)
gdb-peda$ x/26gx 0x555555754dc8
0x555555754dc8: 0x0000000000000001 0x0000000000000001
0x555555754dd8: 0x000000000000000c 0x00000000000004e8
0x555555754de8: 0x000000000000000d 0x00000000000006d4
0x555555754df8: 0x0000000000000019 0x0000000000200db8
0x555555754e08: 0x000000000000001b 0x0000000000000008
0x555555754e18: 0x000000000000001a 0x0000000000200dc0
0x555555754e28: 0x000000000000001c 0x0000000000000008
0x555555754e38: 0x000000006ffffef5 0x0000555555554298
0x555555754e48: 0x0000000000000005 0x0000555555554360
0x555555754e58: 0x0000000000000006 0x00005555555542b8
0x555555754e68: 0x000000000000000a 0x0000000000000082
0x555555754e78: 0x000000000000000b 0x0000000000000018
0x555555754e88: 0x0000000000000015 0x00007ffff7ffe140
gdb-peda$ x/2gx 0x00007ffff7ffe140
0x7ffff7ffe140 <_r_debug>: 0x0000000000000001 0x00007ffff7ffe170

Note that the d_tag field of this DEBUG entry is defined as 0x15:

...
#define DT_DEBUG	21		/* For debugging; unspecified */
...

So the r_debug is located at 0x00007ffff7ffe140, where we find the link_map pointer, 0x00007ffff7ffe170, in its r_map field!


3. Finding _dl_runtime_resolve()

Now we have the link_map. How about the _dl_runtime_resolve()? Full RELRO is enabled for our main executable, so it is no longer possible to find the address of _dl_runtime_resolve() in its GOT[2]. However, the same thing is unlikely to happen to our libraries. Libraries contain a lot of functions, and not all of them are going to be used during a single execution, so turning on full RELRO for them (i.e., resolve all function symbols when the program is started), it will be a significant overhead. This means we are likely to find address of _dl_runtime_resolve in the GOT[2] of libraries!

Recall that link_map is a double-linked list. Each node is defined as follows:

struct link_map
  {
    /* These first few members are part of the protocol with the debugger.
       This is the same format used in SVR4.  */

    ElfW(Addr) l_addr;		          /* Difference between the address in the ELF
				                        file and the addresses in memory. */
    char *l_name;		               /* Absolute file name object was found in. */
    ElfW(Dyn) *l_ld;		            /* Dynamic section of the shared object. */
    struct link_map *l_next, *l_prev;   /* Chain of loaded objects. */

    /* All following members are internal to the dynamic linker.
       They may change without notice. */
...

The link_map is a pretty long data structure. Last time we’ve met one of its very useful fields, l_info, but this time we only focus on its first five fields, which are, as commented, part of the SVR4 protocol.

Starting from 0x7ffff7ffe140, we quickly walk through the double-linked list. l_next fields are highlighted. l_name, as well as the corresponding strings, are also highlighted:

gdb-peda$ x/5gx 0x00007ffff7ffe170
0x7ffff7ffe170: 0x0000555555554000 0x00007ffff7ffe700
0x7ffff7ffe180: 0x0000555555754dc8 0x00007ffff7ffe710
0x7ffff7ffe190: 0x0000000000000000
gdb-peda$ x/s 0x00007ffff7ffe700
0x7ffff7ffe700: “”
gdb-peda$ x/5gx 0x00007ffff7ffe710
0x7ffff7ffe710: 0x00007ffff7ffa000 0x00007ffff7ffebb0
0x7ffff7ffe720: 0x00007ffff7ffa348 0x00007ffff7fed000
0x7ffff7ffe730: 0x00007ffff7ffe170
gdb-peda$ x/s 0x00007ffff7ffebb0
0x7ffff7ffebb0: “linux-vdso.so.1”
gdb-peda$ x/5gx 0x00007ffff7fed000
0x7ffff7fed000: 0x00007ffff79e4000 0x00007ffff7ffedd0
0x7ffff7fed010: 0x00007ffff7dceb80 0x00007ffff7ffd9f0
0x7ffff7fed020: 0x00007ffff7ffe710
gdb-peda$ x/s 0x00007ffff7ffedd0
0x7ffff7ffedd0: “/lib/x86_64-linux-gnu/libc.so.6”
gdb-peda$ x/5gx 0x00007ffff7ffd9f0
0x7ffff7ffd9f0 <_rtld_global+2448>: 0x00007ffff7dd5000 0x0000555555554238
0x7ffff7ffda00 <_rtld_global+2464>: 0x00007ffff7ffce68 0x0000000000000000
0x7ffff7ffda10 <_rtld_global+2480>: 0x00007ffff7fed000
gdb-peda$ x/s 0x0000555555554238
0x555555554238: “/lib64/ld-linux-x86-64.so.2”

As we can see, there are totally four ELF objects loaded into the memory, including linux-vdso.so.1, /lib/x86_64-linux-gnu/libc.so.6, /lib64/ld-linux-x86-64.so.2, as well as the main executable. Note that the l_name string of our main executable turned out to be an empty string, maybe the dynamic loader does not care about how I name my binary. 🙁

Anyway, now we can try to find _dl_runtime_resolve() inside GOT[2] of our binaries! I hope they didn’t turn on full RELRO

Let’s take our old friend, libc.so.6. In the l_ld field of its node we have its .dynamic section address, 0x00007ffff7dceb80. By looking up the PLTGOT entry inside .dynamic we can locate the GOT of libc.so.6:

gdb-peda$ x/24gx 0x00007ffff7dceb80
0x7ffff7dceb80: 0x0000000000000001 0x0000000000005d8a
0x7ffff7dceb90: 0x000000000000000e 0x0000000000005d9f
0x7ffff7dceba0: 0x000000000000000c 0x0000000000021920
0x7ffff7dcebb0: 0x0000000000000019 0x00000000003e7630
0x7ffff7dcebc0: 0x000000000000001b 0x0000000000000008
0x7ffff7dcebd0: 0x0000000000000004 0x00007ffff7bc7638
0x7ffff7dcebe0: 0x000000006ffffef5 0x00007ffff79e42b8
0x7ffff7dcebf0: 0x0000000000000005 0x00007ffff79f59d0
0x7ffff7dcec00: 0x0000000000000006 0x00007ffff79e7ee8
0x7ffff7dcec10: 0x000000000000000a 0x0000000000005ede
0x7ffff7dcec20: 0x000000000000000b 0x0000000000000018
0x7ffff7dcec30: 0x0000000000000003 0x00007ffff7dcf000

Note the d_tag field of this PLTGOT entry is defined as 0x3:

...
#define DT_PLTGOT	3		/* Processor defined value */
...

Let’s take a look at GOT[2], then:

gdb-peda$ x/3gx 0x00007ffff7dcf000
0x7ffff7dcf000: 0x00000000003eab80 0x00007ffff7fed000
0x7ffff7dcf010: 0x00007ffff7dec680
gdb-peda$ xinfo 0x00007ffff7dec680
0x7ffff7dec680 (<_dl_runtime_resolve_xsave>: push rbx)
Virtual memory mapping:
Start : 0x00007ffff7dd5000
End : 0x00007ffff7dfc000
Offset: 0x17680
Perm : r-xp
Name : /lib/x86_64-linux-gnu/ld-2.27.so

Nice.


4. Conclusion

As well as that r_debug data structure is there, as well as not all loaded libraries enable full RELRO, we can still find addresses of link_map and _dl_runtime_resolve(), even if our main executable enables full RELRO.