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.