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.