Next Previous Contents

14. ELF

On Linux, and most other Unix-like systems, executables tend to be in the ELF format. (Ancient formats like a.out are still found occasionally.) Examine ELF headers on an executable using readelf or objdump. A story on how to create a tiny ELF executable.

14.1 An ELF virus

A virus writer must know the structure of the executables of his target systems, in order to be able to write code that infects executables. For Linux with ELF, the research was done by Silvio Cesare. His paper Unix viruses constructs an ELF virus. There is further work in this direction. See, e.g., ELF PLT infection, Cheating the ELF, The Cerberus ELF Interface.

14.2 Defeating the 'noexec' mount option

If a filesystem was mounted with the 'noexec' mount option, then programs on that filesystems cannot be executed.

# mount /dev/sda1 /zip -o noexec
% /zip/hello
Permission denied
On Linux before 2.4.25 / 2.6.0, it was very easy to defeat this mount option:
% /lib/ld-linux.so.2 /zip/hello
Hello!
Under later kernels, this was made more difficult, but not fixed.
% /lib/ld-linux.so.2 /zip/hello
/zip/hello: error while loading shared libraries: /zip/hello: failed to map segment from shared object: Operation not permitted
% ./fixelf /zip/hello
% /lib/ld-linux.so.2 /zip/hello
Hello!
% cat /proc/version
Linux version 2.6.19

The tiny utility fixelf removes the PF_X flag from the ELF program headers:

/* fixelf.c */
...
        Elf32_Ehdr *eh;
        Elf32_Phdr *ph;
...
        eh = (Elf32_Ehdr *) program;
...
        for (i=0; i<eh->e_phnum; i++) {
                ph = (Elf32_Phdr *)(program + eh->e_phoff + i*eh->e_phentsize);
                ph->p_flags &= ~PF_X;
        }
...

Today (2.6.21) this still works.

14.3 ELF auxiliary vectors

On the stack, past argv pointers and envp pointers, one finds the ELF auxiliary vectors. (Cf. Stack Layout.) They can be examined by giving the loader LD_SHOW_AUXV=1 in the environment.

% LD_SHOW_AUXV=1 ./foo
AT_SYSINFO:      0x55573420
AT_SYSINFO_EHDR: 0x55573000
AT_HWCAP:    fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe
AT_PAGESZ:       4096
AT_CLKTCK:       100
AT_PHDR:         0x8048034
AT_PHENT:        32
AT_PHNUM:        8
AT_BASE:         0x55555000
AT_FLAGS:        0x0
AT_ENTRY:        0x8048360
AT_UID:          1000
AT_EUID:         1000
AT_GID:          1000
AT_EGID:         1000
AT_SECURE:       0
AT_RANDOM:       0xffb86d8b
AT_EXECFN:       ./foo
AT_PLATFORM:     i686
One finds at AT_SYSINFO the entry point for vsyscalls. E.g.:
#include <stdio.h>
#include <elf.h>

unsigned int sys, pid;

int main(int argc, char **argv, char **envp) {
        Elf32_auxv_t *auxv;

        /* walk past all env pointers */
        while (*envp++ != NULL)
                ;
        /* and find ELF auxiliary vectors (if this was an ELF binary) */
        auxv = (Elf32_auxv_t *) envp;

        for ( ; auxv->a_type != AT_NULL; auxv++) {
                if (auxv->a_type == AT_SYSINFO) {
                        sys = auxv->a_un.a_val;
                        break;
                }
        }

        __asm__(
"               movl $20, %eax  \n"     /* getpid system call */
"               call *sys       \n"     /* vsyscall */
"               movl %eax, pid  \n"     /* get result */
        );
        printf("pid is %d\n", pid);
        return 0;
}

Glibc secure mode

Among the entries in the table of ELF auxiliary vectors are the values of types AT_SECURE, AT_UID, AT_EUID, AT_GID, AT_EGID. (Let us call them at_secure etc.) Glibc decides to go into secure mode (and not honour most environment variables) when at_secure || (at_uid != at_euid) || (at_gid != at_egid). If the appropriate ELF values are not present, then the condition (uid != euid) || (gid != egid) is used with values obtained from system calls. The AT_SECURE field allows the kernel to suggest secure mode based on capability settings or the decisions of security modules.


Next Previous Contents