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.
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.
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 deniedOn 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.
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: i686One 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; }
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.