3

I was trying to control uninitialized memory as given in this blog post. It says uninitialized memory could be initialized using LD_PRELOAD.

example:

$ export LD_PRELOAD=`perl -e 'print "A"x10000'`

and then running a test program.

LD_PRELOAD are actually loaded at runtime, ld.so copies the name of each library onto the stack prior to executing the program, and doesn’t clean up after itself. By specifying a very long LD_PRELOAD variable and executing a binary, a portion of the stack will be overwritten with part of the LD_PRELOAD variable during linking, and it will stay that way once execution of the program begins, even on setuid binaries, where the library itself is not loaded.

I can get the results in 3.0.0-12-generic-based Ubuntu but not in 4.6.0-kali1-686-pae.

Are there any security implementations newly introduced?

Unobserved results 4.6.0-kali1-686-pae.

4.6.0-kali1-686-pae.

Please tell if more info is required for this question ? :-)

1 Answer 1

3

That's not how LD_PRELOAD is meant to work*. And I'm really struggling to find how it could ever worked. When I perform the following on the latest linux kernel (4.7), latest glibc (2.27) and latest ld (binutils 2.27) I get the following:

$ LD_PRELOAD='0000000' less pp.c
ERROR: ld.so: object '0000000' from LD_PRELOAD cannot be preloaded (cannot open shared object file): ignored.

[4]+  Stopped                 LD_PRELOAD='0000000' less pp.c

So yeah, ld simply complains that the object (here 0x00000) is not an ELF. This comes from elf/rtld.c in glibc, here:

static unsigned int
do_preload (const char *fname, struct link_map *main_map, const char *where)
{
  const char *objname;
  const char *err_str = NULL;
  struct map_args args;
  bool malloced;

  args.str = fname;
  args.loader = main_map;
  args.mode = __RTLD_SECURE;

  unsigned int old_nloaded = GL(dl_ns)[LM_ID_BASE]._ns_nloaded;

  (void) _dl_catch_error (&objname, &err_str, &malloced, map_doit, &args);
  if (__glibc_unlikely (err_str != NULL))
    {
      _dl_error_printf ("\
ERROR: ld.so: object '%s' from %s cannot be preloaded (%s): ignored.\n",
            fname, where, err_str);
      /* No need to call free, this is still before
     the libc's malloc is used.  */
    }
  else if (GL(dl_ns)[LM_ID_BASE]._ns_nloaded != old_nloaded)
    /* It is no duplicate.  */
    return 1;

  /* Nothing loaded.  */
  return 0;
}

do_preload is called for every object in LD_PRELOAD (separated either by space or colon). Now ubuntu 3.0.0 was released around July 2011, at that time the latest glibc was 2.15; I looked at do_preload in glibc-2.15 and found the exact same code.

Now, maybe the ubuntu guys used an old glibc, say glibc-2.11 (November 2009, and that's pushing it far). If you look at that code, you see that do_preload is still the same. That code has been added in 1998 and never changed.

The only thing I can think of are features added by ubuntu at the time (but that's pretty dubious). It should have nothing to do with the linux kernel since it is the linker's work to add the correct shared objects (Just in case, I went to one of those code indexers for the kernel code and checked all 3.x and 4.x kernels. And, as expected, LD_PRELOAD is not used anywhere).


The only thing I can do is to argue about how LD_PRELOAD should be used. And how to achieve a similar effect taking into consideration the above excerpt from glibc. The first option to get that shellcode into memory uses LD_PRELOAD properly (but that is very different from what they do in that blog post), the second option uses the environment (which I believe is what they are seeing in the blog post and misunderstanding it as a loaded shared object).

Option 1: use a real shared object in LD_PRELOAD

The current ld will check whether the object pointed by LD_PRELOAD is an ELF, i.e. that it starts with \x7fELF. A better way to achieve the loading of specific code into a program's memory is to create an ELF yourself and then point to it with LD_PRELOAD:

$ perl -e 'print "int yay(void){char *x=\"" . "A"x10000 . "\"; return 0;}\n"' > pp.c
$ gcc -o pp.so -fPIC -shared pp.c
$ LD_PRELOAD=./pp.so less pp.c
# And Ctrl+Z to send SIGTSTP
$ jobs -l
[1]+ 15476 Stopped                 LD_PRELOAD=./pp.so less pp.c
$ cat /proc/15476/maps
00400000-00422000 r-xp 00000000 08:03 3811056                            /usr/bin/less
00621000-00622000 r--p 00021000 08:03 3811056                            /usr/bin/less
00622000-00626000 rw-p 00022000 08:03 3811056                            /usr/bin/less
00626000-0062a000 rw-p 00000000 00:00 0 
01336000-01357000 rw-p 00000000 00:00 0                                  [heap]
7f0328272000-7f0328525000 r--p 00000000 08:03 3845397                    /usr/lib/locale/locale-archive
7f0328525000-7f032853d000 r-xp 00000000 08:03 3804385                    /usr/lib/libpthread-2.24.so
7f032853d000-7f032873c000 ---p 00018000 08:03 3804385                    /usr/lib/libpthread-2.24.so
7f032873c000-7f032873d000 r--p 00017000 08:03 3804385                    /usr/lib/libpthread-2.24.so
7f032873d000-7f032873e000 rw-p 00018000 08:03 3804385                    /usr/lib/libpthread-2.24.so
7f032873e000-7f0328742000 rw-p 00000000 00:00 0 
7f0328742000-7f03288d7000 r-xp 00000000 08:03 3804421                    /usr/lib/libc-2.24.so
7f03288d7000-7f0328ad6000 ---p 00195000 08:03 3804421                    /usr/lib/libc-2.24.so
7f0328ad6000-7f0328ada000 r--p 00194000 08:03 3804421                    /usr/lib/libc-2.24.so
7f0328ada000-7f0328adc000 rw-p 00198000 08:03 3804421                    /usr/lib/libc-2.24.so
7f0328adc000-7f0328ae0000 rw-p 00000000 00:00 0 
7f0328ae0000-7f0328b52000 r-xp 00000000 08:03 3811050                    /usr/lib/libpcre.so.1.2.7
7f0328b52000-7f0328d51000 ---p 00072000 08:03 3811050                    /usr/lib/libpcre.so.1.2.7
7f0328d51000-7f0328d52000 r--p 00071000 08:03 3811050                    /usr/lib/libpcre.so.1.2.7
7f0328d52000-7f0328d53000 rw-p 00072000 08:03 3811050                    /usr/lib/libpcre.so.1.2.7
7f0328d53000-7f0328dba000 r-xp 00000000 08:03 3804648                    /usr/lib/libncursesw.so.6.0
7f0328dba000-7f0328fba000 ---p 00067000 08:03 3804648                    /usr/lib/libncursesw.so.6.0
7f0328fba000-7f0328fbe000 r--p 00067000 08:03 3804648                    /usr/lib/libncursesw.so.6.0
7f0328fbe000-7f0328fc0000 rw-p 0006b000 08:03 3804648                    /usr/lib/libncursesw.so.6.0
7f0328fc0000-7f0328fc3000 r-xp 00000000 08:05 3801272                    /home/grochmal/tmp/pp.so
7f0328fc3000-7f03291c2000 ---p 00003000 08:05 3801272                    /home/grochmal/tmp/pp.so
7f03291c2000-7f03291c3000 r--p 00002000 08:05 3801272                    /home/grochmal/tmp/pp.so
7f03291c3000-7f03291c4000 rw-p 00003000 08:05 3801272                    /home/grochmal/tmp/pp.so
7f03291c4000-7f03291e7000 r-xp 00000000 08:03 3804420                    /usr/lib/ld-2.24.so
7f03293b0000-7f03293b2000 rw-p 00000000 00:00 0 
7f03293e4000-7f03293e6000 rw-p 00000000 00:00 0 
7f03293e6000-7f03293e7000 r--p 00022000 08:03 3804420                    /usr/lib/ld-2.24.so
7f03293e7000-7f03293e8000 rw-p 00023000 08:03 3804420                    /usr/lib/ld-2.24.so
7f03293e8000-7f03293e9000 rw-p 00000000 00:00 0 
7fff77a97000-7fff77ab8000 rw-p 00000000 00:00 0                          [stack]
7fff77bb5000-7fff77bb7000 r--p 00000000 00:00 0                          [vvar]
7fff77bb7000-7fff77bb9000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

The important bit that /proc/<PID>/maps tells us is the memory location of the object, the "AAAAA...AAAA" string should be somewhere in there:

7f0328fc0000-7f0328fc3000 r-xp 00000000 08:05 3801272                    /home/grochmal/tmp/pp.so
7f0328fc3000-7f03291c2000 ---p 00003000 08:05 3801272                    /home/grochmal/tmp/pp.so
7f03291c2000-7f03291c3000 r--p 00002000 08:05 3801272                    /home/grochmal/tmp/pp.so
7f03291c3000-7f03291c4000 rw-p 00003000 08:05 3801272                    /home/grochmal/tmp/pp.so

We can now use GDB to check it (note that I'm again using the PID I got from jobs -l):

# gdb -q -p 15476
...
warning: Could not load shared library symbols for ./pp.so
...

That line appears if GDB is running in a different working directory than the process was started. But that's fine, we are after memory not symbols.

(gdb) p 0x7f03291c4000 - 0x7f0328fc0000
$1 = 2113536
(gdb) x/2113536x 0x7f0328fc0000

(I took the numbers from the maps and used GDB as a calculator)

That will print a lot of stuff (all memory of the shared object we created, including bits added by ELF and PIC), but will show all the "A"s we are after:

...
0x7f0328fc2ae0: 0x41414141  0x41414141  0x41414141  0x41414141
0x7f0328fc2af0: 0x41414141  0x41414141  0x41414141  0x41414141
0x7f0328fc2b00: 0x41414141  0x41414141  0x41414141  0x41414141
0x7f0328fc2b10: 0x41414141  0x41414141  0x41414141  0x41414141
0x7f0328fc2b20: 0x41414141  0x41414141  0x41414141  0x41414141
0x7f0328fc2b30: 0x41414141  0x41414141  0x41414141  0x41414141
0x7f0328fc2b40: 0x41414141  0x41414141  0x41414141  0x41414141
0x7f0328fc2b50: 0x41414141  0x41414141  0x41414141  0x41414141
0x7f0328fc2b60: 0x41414141  0x41414141  0x41414141  0x41414141
0x7f0328fc2b70: 0x41414141  0x41414141  0x41414141  0x41414141
0x7f0328fc2b80: 0x41414141  0x41414141  0x41414141  0x41414141
0x7f0328fc2b90: 0x41414141  0x41414141  0x41414141  0x41414141
0x7f0328fc2ba0: 0x41414141  0x41414141  0x41414141  0x41414141
...

Option 2: use the environment

Now, this does not need the environment variable to be called LD_PRELOAD. The variable can be anything. Let's start by adding our shellcode ("AAAA...") to LD_PRELOAD as they did in the blog post:

$ LD_PRELOAD=`perl -e 'print "A"x30'` less pp.c
ERROR: ld.so: object 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' from LD_PRELOAD cannot be preloaded (cannot open shared object file): ignored.

[1]+  Stopped                 LD_PRELOAD=`perl -e 'print "A"x30'` less pp.c
$ jobs -l
[1]+  1566 Stopped                 LD_PRELOAD=`perl -e 'print "A"x30'` less pp.c

We got the warning alright, we will ignore it and attach GDB:

# gdb -q -p 1566
(gdb) info variable environ
All variables matching regular expression "environ":

Non-debugging symbols:
0x00007fb7bd5babd8  last_environ
0x00007fb7bd5bbf18  __environ
0x00007fb7bd5bbf18  _environ
0x00007fb7bd5bbf18  environ
0x00007fb7bdcc30c0  __environ
0x00007fb7bdcc30c0  _environ
0x00007fb7bdcc30c0  environ

And search for the environ variable (man 3 environ). We have two of them, that's a peculiarity of kernel 4. environ is a char** so lets look at finding those strings:

(gdb) x/3gx 0x00007fb7bd5bbf18
0x7fb7bd5bbf18 <environ>:   0x00007ffd1c3884a0  0x0000000000000000
0x7fb7bd5bbf28 <buflen.8581>:   0x0000000000000000
(gdb) x/3gx 0x00007fb7bdcc30c0
0x7fb7bdcc30c0 <environ>:   0x00007ffd1c3884a0  0x0000000000000000
0x7fb7bdcc30d0: 0x0000000000000000

Both environ symbols are pointing to the same place in memory (namely 0x00007ffd1c3884a0). Let's see what is in there:

(gdb) x/20gx 0x00007ffd1c3884a0
0x7ffd1c3884a0: 0x00007ffd1c388ba3  0x00007ffd1c388bcd
0x7ffd1c3884b0: 0x00007ffd1c388bd8  0x00007ffd1c388bea
0x7ffd1c3884c0: 0x00007ffd1c388c05  0x00007ffd1c388c15
0x7ffd1c3884d0: 0x00007ffd1c388c27  0x00007ffd1c388c4c
0x7ffd1c3884e0: 0x00007ffd1c388c6c  0x00007ffd1c388c8b
0x7ffd1c3884f0: 0x00007ffd1c388c99  0x00007ffd1c388cd1
0x7ffd1c388500: 0x00007ffd1c388cfa  0x00007ffd1c388d49
0x7ffd1c388510: 0x00007ffd1c388d67  0x00007ffd1c388e01
0x7ffd1c388520: 0x00007ffd1c388e10  0x00007ffd1c388e27
0x7ffd1c388530: 0x00007ffd1c388e38  0x00007ffd1c388ebd

I bet those are strings, let's see:

(gdb) x/s 0x00007ffd1c388ba3
0x7ffd1c388ba3: "LD_PRELOAD=", 'A' <repeats 30 times>
(gdb) x/s 0x00007ffd1c388bcd
0x7ffd1c388bcd: "XDG_VTNR=2"
(gdb) x/s 0x00007ffd1c388bd8
0x7ffd1c388bd8: "XDG_SESSION_ID=c1"

Correct, strings. And we also found our shellcode. We only need to print it in a different way to get the 0x41s:

(gdb) x/20wx 0x00007ffd1c388ba3
0x7ffd1c388ba3: 0x505f444c  0x4f4c4552  0x413d4441  0x41414141
0x7ffd1c388bb3: 0x41414141  0x41414141  0x41414141  0x41414141
0x7ffd1c388bc3: 0x41414141  0x41414141  0x44580041  0x54565f47
0x7ffd1c388bd3: 0x323d524e  0x47445800  0x5345535f  0x4e4f4953
0x7ffd1c388be3: 0x3d44495f  0x54003163  0x3d4d5245  0x74767872

Important note: This has nothing to do with LD_PRELOAD! The program start as:

$ LD_PRELOAD=`perl -e 'print "A"x30'` less pp.c

Could be pretty much done with:

$ YAY=`perl -e 'print "A"x30'` less pp.c

And we would achieve the same result. But I strongly believe this is what they're actually seeing in that blog post.


* I do not have anything running Ubuntu 3.0.0-12-generic, but I suppose that its ld was more permissive and allowed non ELF objects to be loaded. I'm confident that my Arch 4.7 simply rejects the object in LD_PRELOAD if it is not a proper ELF and has no PIC indicators.

11
  • Hi.beautiful answer. That means from your case. When this dynamic library will be loaded. char *x points to the address which has "AAAAAAA..". .. Dump of that location gives the output :-). "So we are able to initialize uninitialized memory". This string should be string literal ,i.e code section of the dynamic library.---> is this assumption correct ? Sep 30, 2016 at 16:31
  • 1
    @souravpunoriyar - Yesterday I was curious and did some analysis on how it could have worked for you in ubuntu 3.0.0. But it was not very fruitful, I have added it to the answer anyway. But yeah, dumping the "AAA..." into the stack adds the string to the .rodata section (which should be fine against something like NX in most cases, since it will prevent only writable .data from executing). Another option would be to manually edit the .fini section or the end of the .text section inside pp.so, where the yay function is located.
    – grochmal
    Sep 30, 2016 at 17:04
  • I exported LD_PRELOAD with pp.so and then executed another program. A/c ldd command it has to load pp.so at runtime.I am not calling yay(). Will it still load , will the code yay() still exist in memory ?... even if i am not using yay. I guess it wont be present. i was trying nebula level11 question. When i came across all these. Sep 30, 2016 at 17:22
  • I am trying to find out through dumps..but not getting it. :-( Sep 30, 2016 at 17:23
  • you can take a look cybergibbons.com/security-2/nebula-walkthrough/… Sep 30, 2016 at 17:25

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .