Ldd Shows Varied Addresses on X86 Linux

ldd shows varied addresses on x86 Linux

Fedora uses address space randomization as part of its various security measures. ldd works by actually loading the shared objects and showing where they end up. Putting the two together results in the given observations.

ldd hex number in parentheses

The hexadecimal numbers are the memory addresses the respective library gets loaded into. See https://stackoverflow.com/a/5130690/637284 for further explanation.

cross compiler ldd

This is a bit of a kluge, but it's the best solution I could find, and it really works quite well for basic use - just save this script as "arm-none-linux-gnueabi-ldd" with your other cross tools.

#!/bin/sh
arm-none-linux-gnueabi-readelf -a $1 | grep "Shared library:"

How can I verify what dynamic linker is used when a program is run?

There is no need to actually run the executable to determine the ELF interpreter that it will use.

We can use static tools and be guaranteed that we can get the full path.

We can use a combination of readelf and ldd.

If we use readelf -a, we can parse the output.


One part of the readelf output:

Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 00000000000002e0 000002e0
000000000000001c 0000000000000000 A 0 0 1

Note the address of the .interp section. It is 0x2e0.


If we open the executable and do a seek to that offset, we can read the ELF interpreter string. For example, here is [what I'll call] fileBad:

000002e0: 2F6C6962 36342F7A 642D6C69 6E75782D  /lib64/zd-linux-
000002f0: 7838362D 36342E73 6F2E3200 00000000 x86-64.so.2.....

Note that the string seems a little odd ... More on that later ...


Under the "Program Headers:" section, we have:

Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040
0x00000000000002a0 0x00000000000002a0 R 0x8
INTERP 0x00000000000002e0 0x00000000000002e0 0x00000000000002e0
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/zd-linux-x86-64.so.2]

Again, note the 0x2e0 file offset. This may be an easier way to get the path to the ELF interpreter.

Now we have the full path to the ELF interpreter.


We can now do ldd /path/to/executable and we'll get a list of the shared libraries it is/will be using. We'll do this for fileGood. Normally, this looks like [redacted]:

linux-vdso.so.1 (0x00007ffc96d43000)
libpython3.7m.so.1.0 => /lib64/libpython3.7m.so.1.0 (0x00007f36d1ee2000)
...
libc.so.6 => /lib64/libc.so.6 (0x00007f36d1ac7000)
/lib64/ld-linux-x86-64.so.2 (0x00007f36d23ff000)
...

That's for a normal executable. Here's the ldd output for fileBad:

linux-vdso.so.1 (0x00007ffc96d43000)
libpython3.7m.so.1.0 => /lib64/libpython3.7m.so.1.0 (0x00007f36d1ee2000)
...
libc.so.6 => /lib64/libc.so.6 (0x00007f36d1ac7000)
/lib64/zd-linux-x86-64.so.2 => /lib64/ld-linux-x86-64.so.2 (0x00007f3f4f821000)
...

Okay, to explain ...

fileGood is a standard executable [/bin/vi on my system]. However, fileBad is a copy that I made where I patched the interpreter path to a non-existent file.

From the readelf data, we know the interpreter path. We can check for existence of that file. If it doesn't exist things are [obviously] bad.

With the interpreter path we got from readelf, we can find the output line from ldd for the interpreter.

For the good file, ldd gave us the simple interpreter resolution:

/lib64/ld-linux-x86-64.so.2 (0x00007f36d23ff000)

For the bad file, ldd gave us:

/lib64/zd-linux-x86-64.so.2 => /lib64/ld-linux-x86-64.so.2 (0x00007f3f4f821000)

So, either ldd or the kernel detected the missing interpreter and substituted the default one.

If we try to exec fileBad from the shell we get:

fileBad: Command not found

If we try to exec fileBad from a C program we get an ENOENT error:

No such file or directory

From this we know that the kernel did not try to use a "default" interpreter when we did an exec* syscall.

So, we now know that the static analysis we did to determine the ELF interpreter path is valid.

We can be assured that the path we came up with is [will be] the path to the ELF interpreter that the kernel will map into the process address space.


For further assurance, if you need to, download the kernel source code. Look in the file: fs/binfmt_elf.c


I think that's sufficient, but to answer the question in your top comment

with that solution would I not have to race to read /proc/<pid>/maps before the program terminates?

There's no need to race.

We can control the fork process. We can set up the child to run under [the syscall] ptrace, so we can control its execution (Note that ptrace is what gdb and strace use).

After we fork, but before we exec, the child can request that the target of the exec sleep until a process attaches to it via ptrace.

So, the parent can examine /proc/pid/maps [or whatever else] before the target executable has executed a single instruction. It can control execution via ptrace [and, eventually, detach to allow the target to run normally].

Is there a way to predict what PID will be generated next and then wait on its creation in /proc?

Given the answer to the first part of your question, this is a bit of a moot point.

There is no way to [accurately] predict the pid of a process we fork. If we could determine the pid that the system would use next, there is no guarantee that we will win the race against another process doing a fork [before us] and "getting" the pid we "thought" would be ours.

how is ld-linux.so* itself linked and loaded?

  1. ld-linux.so* doesn't depends any other libraries. It is runable by itself when loaded to memory.

  2. ldd is a script, it loads the object file via the loader, and the loader checks whether the object is dynamically or statically linked, try this:

LD_TRACE_LOADED_OBJECTS=1 /lib64/ld-linux-x86-64.so.2 /lib64/ld-linux-x86-64.so.2


  1. file reads the magic number or elf header to figure whether the object is dynamically or statically linked, it may output different value from ldd

IMO, ld-linux.so is static linked, because it doesn't have an .interp section which all dynamically linked object must have.



Related Topics



Leave a reply



Submit