libclamav/elf.c
6ed894e0
 /*
939b015c
  *  Copyright (C) 2005 - 2006 Tomasz Kojm <tkojm@clamav.net>
6ed894e0
  *
  *  This program is free software; you can redistribute it and/or modify
bb34cb31
  *  it under the terms of the GNU General Public License version 2 as
  *  published by the Free Software Foundation.
6ed894e0
  *
  *  This program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
48b7b4a7
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  *  MA 02110-1301, USA.
6ed894e0
  */
 
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
 #include <stdio.h>
 #include <string.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <sys/stat.h>
b58fdfc2
 #ifdef	HAVE_UNISTD_H
6ed894e0
 #include <unistd.h>
b58fdfc2
 #endif
6ed894e0
 #include <time.h>
 
 #include "cltypes.h"
 #include "elf.h"
 #include "clamav.h"
01302683
 #include "execs.h"
6ed894e0
 
3071071e
 static inline uint16_t EC16(uint16_t v, uint8_t c)
6ed894e0
 {
3071071e
     if(!c)
961db639
 	return v;
     else
 	return ((v >> 8) + (v << 8));
6ed894e0
 }
 
3071071e
 static inline uint32_t EC32(uint32_t v, uint8_t c)
6ed894e0
 {
3071071e
     if(!c)
961db639
 	return v;
     else
 	return ((v >> 24) | ((v & 0x00FF0000) >> 8) | ((v & 0x0000FF00) << 8) | (v << 24));
6ed894e0
 }
 
939b015c
 static uint32_t cli_rawaddr(uint32_t vaddr, struct elf_program_hdr32 *ph, uint16_t phnum, uint8_t conv, uint8_t *err)
 {
 	uint16_t i, found = 0;
 
 
     for(i = 0; i < phnum; i++) {
 	if(EC32(ph[i].p_vaddr, conv) <= vaddr && EC32(ph[i].p_vaddr, conv) + EC32(ph[i].p_memsz, conv) > vaddr) {
 	    found = 1;
 	    break;
 	}
     }
 
     if(!found) {
 	*err = 1;
 	return 0;
     }
 
     *err = 0;
     return vaddr - EC32(ph[i].p_vaddr, conv) + EC32(ph[i].p_offset, conv);
 }
 
3c91998b
 int cli_scanelf(int desc, cli_ctx *ctx)
6ed894e0
 {
 	struct elf_file_hdr32 file_hdr;
 	struct elf_section_hdr32 *section_hdr;
939b015c
 	struct elf_program_hdr32 *program_hdr;
 	uint16_t shnum, phnum, shentsize, phentsize;
 	uint32_t entry, fentry, shoff, phoff, i;
 	uint8_t conv = 0, err;
 
6ed894e0
 
01302683
     cli_dbgmsg("in cli_scanelf\n");
6ed894e0
 
     if(read(desc, &file_hdr, sizeof(file_hdr)) != sizeof(file_hdr)) {
 	/* Not an ELF file? */
 	cli_dbgmsg("ELF: Can't read file header\n");
 	return CL_CLEAN;
     }
 
     if(memcmp(file_hdr.e_ident, "\x7f\x45\x4c\x46", 4)) {
 	cli_dbgmsg("ELF: Not an ELF file\n");
 	return CL_CLEAN;
     }
 
961db639
     if(file_hdr.e_ident[4] != 1) {
01302683
 	cli_dbgmsg("ELF: 64-bit binaries are not supported (yet)\n");
961db639
 	return CL_CLEAN;
     }
 
     if(file_hdr.e_ident[5] == 1) {
 #if WORDS_BIGENDIAN == 0
 	cli_dbgmsg("ELF: File is little-endian - conversion not required\n");
 #else
 	cli_dbgmsg("ELF: File is little-endian - data conversion enabled\n");
3071071e
 	conv = 1;
961db639
 #endif
     } else {
 #if WORDS_BIGENDIAN == 0
 	cli_dbgmsg("ELF: File is big-endian - data conversion enabled\n");
3071071e
 	conv = 1;
961db639
 #else
 	cli_dbgmsg("ELF: File is big-endian - conversion not required\n");
 #endif
     }
 
3071071e
     switch(EC16(file_hdr.e_type, conv)) {
6ed894e0
 	case 0x0: /* ET_NONE */
 	    cli_dbgmsg("ELF: File type: None\n");
 	    break;
 	case 0x1: /* ET_REL */
 	    cli_dbgmsg("ELF: File type: Relocatable\n");
 	    break;
 	case 0x2: /* ET_EXEC */
 	    cli_dbgmsg("ELF: File type: Executable\n");
 	    break;
 	case 0x3: /* ET_DYN */
 	    cli_dbgmsg("ELF: File type: Core\n");
 	    break;
 	case 0x4: /* ET_CORE */
 	    cli_dbgmsg("ELF: File type: Core\n");
 	    break;
 	default:
3071071e
 	    cli_dbgmsg("ELF: File type: Unknown (%d)\n", EC16(file_hdr.e_type, conv));
6ed894e0
     }
 
3071071e
     switch(EC16(file_hdr.e_machine, conv)) {
6ed894e0
 	/* Due to a huge list, we only include the most popular machines here */
 	case 0x0: /* EM_NONE */
 	    cli_dbgmsg("ELF: Machine type: None\n");
 	    break;
 	case 0x2: /* EM_SPARC */
 	    cli_dbgmsg("ELF: Machine type: SPARC\n");
 	    break;
 	case 0x3: /* EM_386 */
 	    cli_dbgmsg("ELF: Machine type: Intel 80386\n");
 	    break;
 	case 0x4: /* EM_68K */
 	    cli_dbgmsg("ELF: Machine type: Motorola 68000\n");
 	    break;
 	case 0x8: /* EM_MIPS */
 	    cli_dbgmsg("ELF: Machine type: MIPS RS3000\n");
 	    break;
 	case 0x15: /* EM_PARISC */
 	    cli_dbgmsg("ELF: Machine type: HPPA\n");
 	    break;
 	case 0x20: /* EM_PPC */
 	    cli_dbgmsg("ELF: Machine type: PowerPC\n");
 	    break;
 	case 0x21: /* EM_PPC64 */
 	    cli_dbgmsg("ELF: Machine type: PowerPC 64-bit\n");
 	    break;
 	case 0x22: /* EM_S390 */
 	    cli_dbgmsg("ELF: Machine type: IBM S390\n");
 	    break;
 	case 0x40: /* EM_ARM */
 	    cli_dbgmsg("ELF: Machine type: ARM\n");
 	    break;
 	case 0x41: /* EM_FAKE_ALPHA */
 	    cli_dbgmsg("ELF: Machine type: Digital Alpha\n");
 	    break;
 	case 0x43: /* EM_SPARCV9 */
 	    cli_dbgmsg("ELF: Machine type: SPARC v9 64-bit\n");
 	    break;
 	case 0x50: /* EM_IA_64 */
 	    cli_dbgmsg("ELF: Machine type: IA64\n");
 	    break;
 	default:
3071071e
 	    cli_dbgmsg("ELF: Machine type: Unknown (%d)\n", EC16(file_hdr.e_machine, conv));
6ed894e0
     }
 
3071071e
     entry = EC32(file_hdr.e_entry, conv);
939b015c
 
     /* Program headers */
 
     phnum = EC16(file_hdr.e_phnum, conv);
     cli_dbgmsg("ELF: Number of program headers: %d\n", phnum);
     if(phnum > 128) {
 	cli_dbgmsg("ELF: Suspicious number of program headers\n");
         if(DETECT_BROKEN) {
 	    if(ctx->virname)
 		*ctx->virname = "Broken.Executable";
             return CL_VIRUS;
         }
 	return CL_EFORMAT;
     }
 
     if(phnum && entry) {
 
 	phentsize = EC16(file_hdr.e_phentsize, conv);
 	if(phentsize != sizeof(struct elf_program_hdr32)) {
 	    cli_dbgmsg("ELF: phentsize != sizeof(struct elf_program_hdr32)\n");
 	    if(DETECT_BROKEN) {
 		if(ctx->virname)
 		    *ctx->virname = "Broken.Executable";
 		return CL_VIRUS;
 	    }
 	    return CL_EFORMAT;
 	}
 
 	phoff = EC32(file_hdr.e_phoff, conv);
 	cli_dbgmsg("ELF: Program header table offset: %d\n", phoff);
 	if((uint32_t) lseek(desc, phoff, SEEK_SET) != phoff) {
 	    if(DETECT_BROKEN) {
 		if(ctx->virname)
 		    *ctx->virname = "Broken.Executable";
 		return CL_VIRUS;
 	    }
 	    return CL_CLEAN;
 	}
 
 	program_hdr = (struct elf_program_hdr32 *) cli_calloc(phnum, phentsize);
 	if(!program_hdr) {
 	    cli_errmsg("ELF: Can't allocate memory for program headers\n");
 	    return CL_EMEM;
 	}
 
 	cli_dbgmsg("------------------------------------\n");
 
 	for(i = 0; i < phnum; i++) {
 
 	    if(read(desc, &program_hdr[i], sizeof(struct elf_program_hdr32)) != sizeof(struct elf_program_hdr32)) {
 		cli_dbgmsg("ELF: Can't read segment #%d\n", i);
 		cli_dbgmsg("ELF: Possibly broken ELF file\n");
 		free(program_hdr);
 		if(DETECT_BROKEN) {
 		    if(ctx->virname)
 			*ctx->virname = "Broken.Executable";
 		    return CL_VIRUS;
 		}
 		return CL_CLEAN;
 	    }
 
 	    cli_dbgmsg("ELF: Segment #%d\n", i);
 	    cli_dbgmsg("ELF: Segment type: 0x%x\n", EC32(program_hdr[i].p_type, conv));
 	    cli_dbgmsg("ELF: Segment offset: 0x%x\n", EC32(program_hdr[i].p_offset, conv));
 	    cli_dbgmsg("ELF: Segment virtual address: 0x%x\n", EC32(program_hdr[i].p_vaddr, conv));
 	    cli_dbgmsg("ELF: Segment real size: 0x%x\n", EC32(program_hdr[i].p_filesz, conv));
 	    cli_dbgmsg("ELF: Segment virtual size: 0x%x\n", EC32(program_hdr[i].p_memsz, conv));
 	    cli_dbgmsg("------------------------------------\n");
 	}
 
 	fentry = cli_rawaddr(entry, program_hdr, phnum, conv, &err);
 	free(program_hdr);
 	if(err) {
 	    cli_dbgmsg("ELF: Can't calculate file offset of entry point\n");
 	    if(DETECT_BROKEN) {
 		if(ctx->virname)
 		    *ctx->virname = "Broken.Executable";
 		return CL_VIRUS;
 	    }
 	    return CL_EFORMAT;
 	}
 	cli_dbgmsg("ELF: Entry point address: 0x%.8x\n", entry);
 	cli_dbgmsg("ELF: Entry point offset: 0x%.8x (%d)\n", fentry, fentry);
     }
 
     /* Sections */
6ed894e0
 
3071071e
     shnum = EC16(file_hdr.e_shnum, conv);
6ed894e0
     cli_dbgmsg("ELF: Number of sections: %d\n", shnum);
     if(shnum > 256) {
 	cli_dbgmsg("ELF: Suspicious number of sections\n");
         if(DETECT_BROKEN) {
3c91998b
 	    if(ctx->virname)
 		*ctx->virname = "Broken.Executable";
6ed894e0
             return CL_VIRUS;
         }
 	return CL_EFORMAT;
     }
 
3071071e
     shentsize = EC16(file_hdr.e_shentsize, conv);
6ed894e0
     if(shentsize != sizeof(struct elf_section_hdr32)) {
01302683
 	cli_dbgmsg("ELF: shentsize != sizeof(struct elf_section_hdr32)\n");
6ed894e0
         if(DETECT_BROKEN) {
3c91998b
 	    if(ctx->virname)
 		*ctx->virname = "Broken.Executable";
6ed894e0
             return CL_VIRUS;
         }
 	return CL_EFORMAT;
     }
 
3071071e
     shoff = EC32(file_hdr.e_shoff, conv);
6ed894e0
     cli_dbgmsg("ELF: Section header table offset: %d\n", shoff);
e12c29d2
     if((uint32_t) lseek(desc, shoff, SEEK_SET) != shoff) {
6ed894e0
 	/* Possibly broken end of file */
         if(DETECT_BROKEN) {
3c91998b
 	    if(ctx->virname)
 		*ctx->virname = "Broken.Executable";
6ed894e0
             return CL_VIRUS;
         }
 	return CL_CLEAN;
     }
 
     section_hdr = (struct elf_section_hdr32 *) cli_calloc(shnum, shentsize);
     if(!section_hdr) {
961db639
 	cli_errmsg("ELF: Can't allocate memory for section headers\n");
6ed894e0
 	return CL_EMEM;
     }
 
     cli_dbgmsg("------------------------------------\n");
 
     for(i = 0; i < shnum; i++) {
 
 	if(read(desc, &section_hdr[i], sizeof(struct elf_section_hdr32)) != sizeof(struct elf_section_hdr32)) {
             cli_dbgmsg("ELF: Can't read section header\n");
             cli_dbgmsg("ELF: Possibly broken ELF file\n");
             free(section_hdr);
             if(DETECT_BROKEN) {
3c91998b
                 if(ctx->virname)
                     *ctx->virname = "Broken.Executable";
6ed894e0
                 return CL_VIRUS;
             }
             return CL_CLEAN;
         }
 
 	cli_dbgmsg("ELF: Section %d\n", i);
3071071e
 	cli_dbgmsg("ELF: Section offset: %d\n", EC32(section_hdr[i].sh_offset, conv));
 	cli_dbgmsg("ELF: Section size: %d\n", EC32(section_hdr[i].sh_size, conv));
6ed894e0
 
3071071e
 	switch(EC32(section_hdr[i].sh_type, conv)) {
6ed894e0
 	    case 0x6: /* SHT_DYNAMIC */
 		cli_dbgmsg("ELF: Section type: Dynamic linking information\n");
 		break;
 	    case 0xb: /* SHT_DYNSYM */
 		cli_dbgmsg("ELF: Section type: Symbols for dynamic linking\n");
 		break;
 	    case 0xf: /* SHT_FINI_ARRAY */
 		cli_dbgmsg("ELF: Section type: Array of pointers to termination functions\n");
 		break;
 	    case 0x5: /* SHT_HASH */
 		cli_dbgmsg("ELF: Section type: Symbol hash table\n");
 		break;
 	    case 0xe: /* SHT_INIT_ARRAY */
 		cli_dbgmsg("ELF: Section type: Array of pointers to initialization functions\n");
 		break;
 	    case 0x8: /* SHT_NOBITS */
 		cli_dbgmsg("ELF: Section type: Empty section (NOBITS)\n");
 		break;
 	    case 0x7: /* SHT_NOTE */
 		cli_dbgmsg("ELF: Section type: Note section\n");
 		break;
 	    case 0x0: /* SHT_NULL */
 		cli_dbgmsg("ELF: Section type: Null (no associated section)\n");
 		break;
 	    case 0x10: /* SHT_PREINIT_ARRAY */
 		cli_dbgmsg("ELF: Section type: Array of pointers to preinit functions\n");
 		break;
 	    case 0x1: /* SHT_PROGBITS */
 		cli_dbgmsg("ELF: Section type: Program information\n");
 		break;
 	    case 0x9: /* SHT_REL */
 		cli_dbgmsg("ELF: Section type: Relocation entries w/o explicit addends\n");
 		break;
 	    case 0x4: /* SHT_RELA */
 		cli_dbgmsg("ELF: Section type: Relocation entries with explicit addends\n");
 		break;
 	    case 0x3: /* SHT_STRTAB */
 		cli_dbgmsg("ELF: Section type: String table\n");
 		break;
 	    case 0x2: /* SHT_SYMTAB */
 		cli_dbgmsg("ELF: Section type: Symbol table\n");
 		break;
 	    case 0x6ffffffd: /* SHT_GNU_verdef */
 		cli_dbgmsg("ELF: Section type: Provided symbol versions\n");
 		break;
 	    case 0x6ffffffe: /* SHT_GNU_verneed */
 		cli_dbgmsg("ELF: Section type: Required symbol versions\n");
 		break;
 	    case 0x6fffffff: /* SHT_GNU_versym */
 		cli_dbgmsg("ELF: Section type: Symbol Version Table\n");
 		break;
 	    default :
 		cli_dbgmsg("ELF: Section type: Unknown\n");
 	}
 
3071071e
 	if(EC32(section_hdr[i].sh_flags, conv) & 0x1) /* SHF_WRITE */
6ed894e0
 	    cli_dbgmsg("ELF: Section contains writable data\n");
 
3071071e
 	if(EC32(section_hdr[i].sh_flags, conv) & 0x2) /* SHF_ALLOC */
6ed894e0
 	    cli_dbgmsg("ELF: Section occupies memory\n");
 
3071071e
 	if(EC32(section_hdr[i].sh_flags, conv) & 0x4) /* SHF_EXECINSTR */
6ed894e0
 	    cli_dbgmsg("ELF: Section contains executable code\n");
 
 	cli_dbgmsg("------------------------------------\n");
     }
 
     free(section_hdr);
     return CL_CLEAN;
 }
01302683
 
 int cli_elfheader(int desc, struct cli_exe_info *elfinfo)
 {
 	struct elf_file_hdr32 file_hdr;
 	struct elf_section_hdr32 *section_hdr;
939b015c
 	struct elf_program_hdr32 *program_hdr;
 	uint16_t shnum, phnum, shentsize, phentsize, i;
 	uint32_t entry, fentry = 0, shoff, phoff;
 	uint8_t conv = 0, err;
 
01302683
 
     cli_dbgmsg("in cli_elfheader\n");
 
     if(read(desc, &file_hdr, sizeof(file_hdr)) != sizeof(file_hdr)) {
 	/* Not an ELF file? */
 	cli_dbgmsg("ELF: Can't read file header\n");
 	return -1;
     }
 
     if(memcmp(file_hdr.e_ident, "\x7f\x45\x4c\x46", 4)) {
 	cli_dbgmsg("ELF: Not an ELF file\n");
 	return -1;
     }
 
     if(file_hdr.e_ident[4] != 1) {
 	cli_dbgmsg("ELF: 64-bit binaries are not supported (yet)\n");
 	return -1;
     }
 
     if(file_hdr.e_ident[5] == 1) {
 #if WORDS_BIGENDIAN == 1
3071071e
 	conv = 1;
01302683
 #endif
     } else {
 #if WORDS_BIGENDIAN == 0
3071071e
 	conv = 1;
01302683
 #endif
     }
 
939b015c
     phnum = EC16(file_hdr.e_phnum, conv);
     if(phnum > 128) {
 	cli_dbgmsg("ELF: Suspicious number of program headers\n");
 	return -1;
     }
3071071e
     entry = EC32(file_hdr.e_entry, conv);
01302683
 
939b015c
     if(phnum && entry) {
 	phentsize = EC16(file_hdr.e_phentsize, conv);
 	if(phentsize != sizeof(struct elf_program_hdr32)) {
 	    cli_dbgmsg("ELF: phentsize != sizeof(struct elf_program_hdr32)\n");
 	    return -1;
 	}
 
 	phoff = EC32(file_hdr.e_phoff, conv);
 	if((uint32_t) lseek(desc, phoff, SEEK_SET) != phoff) {
 	    return -1;
 	}
 
 	program_hdr = (struct elf_program_hdr32 *) cli_calloc(phnum, phentsize);
 	if(!program_hdr) {
 	    cli_errmsg("ELF: Can't allocate memory for program headers\n");
 	    return -1;
 	}
 
 	for(i = 0; i < phnum; i++) {
 	    if(read(desc, &program_hdr[i], sizeof(struct elf_program_hdr32)) != sizeof(struct elf_program_hdr32)) {
 		cli_dbgmsg("ELF: Can't read segment #%d\n", i);
 		free(program_hdr);
 		return -1;
 	    }
 	}
 
 	fentry = cli_rawaddr(entry, program_hdr, phnum, conv, &err);
 	free(program_hdr);
 	if(err) {
 	    cli_dbgmsg("ELF: Can't calculate file offset of entry point\n");
 	    return -1;
 	}
     }
 
     elfinfo->ep = fentry;
 
3071071e
     shnum = EC16(file_hdr.e_shnum, conv);
01302683
     if(shnum > 256) {
 	cli_dbgmsg("ELF: Suspicious number of sections\n");
 	return -1;
     }
     elfinfo->nsections = shnum;
 
3071071e
     shentsize = EC16(file_hdr.e_shentsize, conv);
01302683
     if(shentsize != sizeof(struct elf_section_hdr32)) {
 	cli_dbgmsg("ELF: shentsize != sizeof(struct elf_section_hdr32)\n");
 	return -1;
     }
 
3071071e
     shoff = EC32(file_hdr.e_shoff, conv);
e12c29d2
     if((uint32_t) lseek(desc, shoff, SEEK_SET) != shoff) {
01302683
 	/* Possibly broken end of file */
 	return -1;
     }
 
     elfinfo->section = (struct cli_exe_section *) cli_calloc(elfinfo->nsections, sizeof(struct cli_exe_section));
     if(!elfinfo->section) {
 	cli_dbgmsg("ELF: Can't allocate memory for section headers\n");
 	return -1;
     }
 
     section_hdr = (struct elf_section_hdr32 *) cli_calloc(shnum, shentsize);
     if(!section_hdr) {
 	cli_errmsg("ELF: Can't allocate memory for section headers\n");
841161e0
 	free(elfinfo->section);
 	elfinfo->section = NULL;
01302683
 	return -1;
     }
 
     for(i = 0; i < shnum; i++) {
 
 	if(read(desc, &section_hdr[i], sizeof(struct elf_section_hdr32)) != sizeof(struct elf_section_hdr32)) {
             free(section_hdr);
 	    free(elfinfo->section);
841161e0
 	    elfinfo->section = NULL;
01302683
             return -1;
         }
 
3071071e
 	elfinfo->section[i].rva = EC32(section_hdr[i].sh_addr, conv);
 	elfinfo->section[i].raw = EC32(section_hdr[i].sh_offset, conv);
 	elfinfo->section[i].rsz = EC32(section_hdr[i].sh_size, conv);
01302683
     }
 
     free(section_hdr);
     return 0;
 }