static uint64_t cli_rawaddr64(uint64_t vaddr, struct elf_program_hdr64 *ph, uint16_t phnum, uint8_t conv, uint8_t *err) { uint16_t i, found = 0; for(i = 0; i < phnum; i++) { if(EC64(ph[i].p_vaddr, conv) <= vaddr && EC64(ph[i].p_vaddr, conv) + EC64(ph[i].p_memsz, conv) > vaddr) { found = 1; break; } } if(!found) { *err = 1; return 0; } *err = 0; return vaddr - EC64(ph[i].p_vaddr, conv) + EC64(ph[i].p_offset, conv); }
/* Return converted endian-fixed header, or error code */ static int cli_elf_fileheader(cli_ctx *ctx, fmap_t *map, union elf_file_hdr *file_hdr, uint8_t *do_convert, uint8_t *is64) { uint8_t format64, conv; /* Load enough for smaller header first */ if(fmap_readn(map, file_hdr, 0, sizeof(struct elf_file_hdr32)) != sizeof(struct elf_file_hdr32)) { /* Not an ELF file? */ cli_dbgmsg("ELF: Can't read file header\n"); return CL_BREAK; } if(memcmp(file_hdr->hdr64.e_ident, "\x7f\x45\x4c\x46", 4)) { cli_dbgmsg("ELF: Not an ELF file\n"); return CL_BREAK; } switch(file_hdr->hdr64.e_ident[4]) { case 1: cli_dbgmsg("ELF: ELF class 1 (32-bit)\n"); format64 = 0; break; case 2: cli_dbgmsg("ELF: ELF class 2 (64-bit)\n"); format64 = 1; break; default: cli_dbgmsg("ELF: Unknown ELF class (%u)\n", file_hdr->hdr64.e_ident[4]); return CL_EFORMAT; } /* Need to know to endian convert */ if(file_hdr->hdr64.e_ident[5] == 1) { #if WORDS_BIGENDIAN == 0 if(ctx) cli_dbgmsg("ELF: File is little-endian - conversion not required\n"); conv = 0; #else if(ctx) cli_dbgmsg("ELF: File is little-endian - data conversion enabled\n"); conv = 1; #endif } else { #if WORDS_BIGENDIAN == 0 if(ctx) cli_dbgmsg("ELF: File is big-endian - data conversion enabled\n"); conv = 1; #else if(ctx) cli_dbgmsg("ELF: File is big-endian - conversion not required\n"); conv = 0; #endif } *do_convert = conv; *is64 = format64; /* Solve bit-size and conversion pronto */ file_hdr->hdr64.e_type = EC16(file_hdr->hdr64.e_type, conv); file_hdr->hdr64.e_machine = EC16(file_hdr->hdr64.e_machine, conv); file_hdr->hdr64.e_version = EC32(file_hdr->hdr64.e_version, conv); if(format64) { /* Read rest of 64-bit header */ if(fmap_readn(map, file_hdr->hdr32.pad, sizeof(struct elf_file_hdr32), ELF_HDR_SIZEDIFF) != ELF_HDR_SIZEDIFF) { /* Not an ELF file? */ cli_dbgmsg("ELF: Can't read file header\n"); return CL_BREAK; } /* Now endian convert, if needed */ if(conv) { file_hdr->hdr64.e_entry = EC64(file_hdr->hdr64.e_entry, conv); file_hdr->hdr64.e_phoff = EC64(file_hdr->hdr64.e_phoff, conv); file_hdr->hdr64.e_shoff = EC64(file_hdr->hdr64.e_shoff, conv); file_hdr->hdr64.e_flags = EC32(file_hdr->hdr64.e_flags, conv); file_hdr->hdr64.e_ehsize = EC16(file_hdr->hdr64.e_ehsize, conv); file_hdr->hdr64.e_phentsize = EC16(file_hdr->hdr64.e_phentsize, conv); file_hdr->hdr64.e_phnum = EC16(file_hdr->hdr64.e_phnum, conv); file_hdr->hdr64.e_shentsize = EC16(file_hdr->hdr64.e_shentsize, conv); file_hdr->hdr64.e_shnum = EC16(file_hdr->hdr64.e_shnum, conv); file_hdr->hdr64.e_shstrndx = EC16(file_hdr->hdr64.e_shstrndx, conv); } } else { /* Convert 32-bit structure, if needed */ if(conv) { file_hdr->hdr32.hdr.e_entry = EC32(file_hdr->hdr32.hdr.e_entry, conv); file_hdr->hdr32.hdr.e_phoff = EC32(file_hdr->hdr32.hdr.e_phoff, conv); file_hdr->hdr32.hdr.e_shoff = EC32(file_hdr->hdr32.hdr.e_shoff, conv); file_hdr->hdr32.hdr.e_flags = EC32(file_hdr->hdr32.hdr.e_flags, conv); file_hdr->hdr32.hdr.e_ehsize = EC16(file_hdr->hdr32.hdr.e_ehsize, conv); file_hdr->hdr32.hdr.e_phentsize = EC16(file_hdr->hdr32.hdr.e_phentsize, conv); file_hdr->hdr32.hdr.e_phnum = EC16(file_hdr->hdr32.hdr.e_phnum, conv); file_hdr->hdr32.hdr.e_shentsize = EC16(file_hdr->hdr32.hdr.e_shentsize, conv); file_hdr->hdr32.hdr.e_shnum = EC16(file_hdr->hdr32.hdr.e_shnum, conv); file_hdr->hdr32.hdr.e_shstrndx = EC16(file_hdr->hdr32.hdr.e_shstrndx, conv); } /* Wipe pad for safety */ memset(file_hdr->hdr32.pad, 0, ELF_HDR_SIZEDIFF); } return CL_CLEAN; }
/* Read 64-bit program headers */ static int cli_elf_ph64(cli_ctx *ctx, fmap_t *map, struct cli_exe_info *elfinfo, struct elf_file_hdr64 *file_hdr, uint8_t conv) { struct elf_program_hdr64 *program_hdr = NULL; uint16_t phnum, phentsize; uint64_t entry, fentry = 0, phoff; uint32_t i; uint8_t err; /* Program headers and Entry */ phnum = file_hdr->e_phnum; cli_dbgmsg("ELF: Number of program headers: %d\n", phnum); if(phnum > 128) { cli_dbgmsg("ELF: Suspicious number of program headers\n"); if(ctx && DETECT_BROKEN) { cli_append_virus(ctx, "Heuristics.Broken.Executable"); return CL_VIRUS; } return CL_EFORMAT; } entry = file_hdr->e_entry; if(phnum && entry) { phentsize = file_hdr->e_phentsize; /* Sanity check */ if (phentsize != sizeof(struct elf_program_hdr64)) { cli_dbgmsg("ELF: phentsize != sizeof(struct elf_program_hdr64)\n"); if(ctx && DETECT_BROKEN) { cli_append_virus(ctx, "Heuristics.Broken.Executable"); return CL_VIRUS; } return CL_EFORMAT; } phoff = file_hdr->e_phoff; if(ctx) { cli_dbgmsg("ELF: Program header table offset: " STDu64 "\n", phoff); } if(phnum) { program_hdr = (struct elf_program_hdr64 *) cli_calloc(phnum, sizeof(struct elf_program_hdr64)); if(!program_hdr) { cli_errmsg("ELF: Can't allocate memory for program headers\n"); return CL_EMEM; } if(ctx) { cli_dbgmsg("------------------------------------\n"); } } for(i = 0; i < phnum; i++) { err = 0; if(fmap_readn(map, &program_hdr[i], phoff, sizeof(struct elf_program_hdr64)) != sizeof(struct elf_program_hdr64)) err = 1; phoff += sizeof(struct elf_program_hdr64); if(err) { cli_dbgmsg("ELF: Can't read segment #%d\n", i); if(ctx) { cli_dbgmsg("ELF: Possibly broken ELF file\n"); } free(program_hdr); if(ctx && DETECT_BROKEN) { cli_append_virus(ctx, "Heuristics.Broken.Executable"); return CL_VIRUS; } return CL_BREAK; } if(ctx) { 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" STDx64 "\n", EC64(program_hdr[i].p_offset, conv)); cli_dbgmsg("ELF: Segment virtual address: 0x" STDx64 "\n", EC64(program_hdr[i].p_vaddr, conv)); cli_dbgmsg("ELF: Segment real size: 0x" STDx64 "\n", EC64(program_hdr[i].p_filesz, conv)); cli_dbgmsg("ELF: Segment virtual size: 0x" STDx64 "\n", EC64(program_hdr[i].p_memsz, conv)); cli_dbgmsg("------------------------------------\n"); } } fentry = cli_rawaddr64(entry, program_hdr, phnum, conv, &err); free(program_hdr); if(err) { cli_dbgmsg("ELF: Can't calculate file offset of entry point\n"); if(ctx && DETECT_BROKEN) { cli_append_virus(ctx, "Heuristics.Broken.Executable"); return CL_VIRUS; } return CL_EFORMAT; } if(ctx) { cli_dbgmsg("ELF: Entry point address: 0x%.16" PRIx64 "\n", entry); cli_dbgmsg("ELF: Entry point offset: 0x%.16" PRIx64 " (" STDi64 ")\n", fentry, fentry); } } if(elfinfo) { elfinfo->ep = fentry; } return CL_CLEAN; }
/* 64-bit version of section header parsing */ static int cli_elf_sh64(cli_ctx *ctx, fmap_t *map, struct cli_exe_info *elfinfo, struct elf_file_hdr64 *file_hdr, uint8_t conv) { struct elf_section_hdr64 *section_hdr = NULL; uint16_t shnum, shentsize; uint32_t i; uint64_t shoff; shnum = file_hdr->e_shnum; cli_dbgmsg("ELF: Number of sections: %d\n", shnum); if(ctx && (shnum > 2048)) { cli_dbgmsg("ELF: Number of sections > 2048, skipping\n"); return CL_BREAK; } else if(elfinfo && (shnum > 256)) { cli_dbgmsg("ELF: Suspicious number of sections\n"); return CL_BREAK; } if(elfinfo) { elfinfo->nsections = shnum; } shentsize = file_hdr->e_shentsize; /* Sanity check */ if(shentsize != sizeof(struct elf_section_hdr64)) { cli_dbgmsg("ELF: shentsize != sizeof(struct elf_section_hdr64)\n"); if(ctx && DETECT_BROKEN) { cli_append_virus(ctx, "Heuristics.Broken.Executable"); return CL_VIRUS; } return CL_EFORMAT; } if(elfinfo && !shnum) { return CL_CLEAN; } shoff = file_hdr->e_shoff; if(ctx) cli_dbgmsg("ELF: Section header table offset: " STDu64 "\n", shoff); if(elfinfo) { elfinfo->section = (struct cli_exe_section *)cli_calloc(shnum, sizeof(struct cli_exe_section)); if(!elfinfo->section) { cli_dbgmsg("ELF: Can't allocate memory for section headers\n"); return CL_EMEM; } } if(shnum) { section_hdr = (struct elf_section_hdr64 *) cli_calloc(shnum, shentsize); if(!section_hdr) { cli_errmsg("ELF: Can't allocate memory for section headers\n"); if(elfinfo) { free(elfinfo->section); elfinfo->section = NULL; } return CL_EMEM; } if(ctx) { cli_dbgmsg("------------------------------------\n"); } } /* Loop over section headers */ for(i = 0; i < shnum; i++) { uint32_t sh_type, sh_flags; if(fmap_readn(map, §ion_hdr[i], shoff, sizeof(struct elf_section_hdr64)) != sizeof(struct elf_section_hdr64)) { cli_dbgmsg("ELF: Can't read section header\n"); if(ctx) { cli_dbgmsg("ELF: Possibly broken ELF file\n"); } free(section_hdr); if(elfinfo) { free(elfinfo->section); elfinfo->section = NULL; } if(ctx && DETECT_BROKEN) { cli_append_virus(ctx, "Heuristics.Broken.Executable"); return CL_VIRUS; } return CL_BREAK; } shoff += sizeof(struct elf_section_hdr64); if(elfinfo) { elfinfo->section[i].rva = EC64(section_hdr[i].sh_addr, conv); elfinfo->section[i].raw = EC64(section_hdr[i].sh_offset, conv); elfinfo->section[i].rsz = EC64(section_hdr[i].sh_size, conv); } if(ctx) { cli_dbgmsg("ELF: Section %u\n", i); cli_dbgmsg("ELF: Section offset: " STDu64 "\n", EC64(section_hdr[i].sh_offset, conv)); cli_dbgmsg("ELF: Section size: " STDu64 "\n", EC64(section_hdr[i].sh_size, conv)); sh_type = EC32(section_hdr[i].sh_type, conv); sh_flags = (uint32_t)(EC64(section_hdr[i].sh_flags, conv) & ELF_SHF_MASK); cli_elf_sectionlog(sh_type, sh_flags); cli_dbgmsg("------------------------------------\n"); } } free(section_hdr); return CL_CLEAN; }
int cli_scanmacho(cli_ctx *ctx, struct cli_exe_info *fileinfo) { struct macho_hdr hdr; struct macho_load_cmd load_cmd; struct macho_segment_cmd segment_cmd; struct macho_segment_cmd64 segment_cmd64; struct macho_section section; struct macho_section64 section64; unsigned int i, j, sect = 0, conv, m64, nsects, matcher = 0; unsigned int arch = 0, ep = 0, err; struct cli_exe_section *sections = NULL; char name[16]; fmap_t *map = *ctx->fmap; ssize_t at; if(fileinfo) matcher = 1; if(fmap_readn(map, &hdr, 0, sizeof(hdr)) != sizeof(hdr)) { cli_dbgmsg("cli_scanmacho: Can't read header\n"); return matcher ? -1 : CL_EFORMAT; } at = sizeof(hdr); if(hdr.magic == 0xfeedface) { conv = 0; m64 = 0; } else if(hdr.magic == 0xcefaedfe) { conv = 1; m64 = 0; } else if(hdr.magic == 0xfeedfacf) { conv = 0; m64 = 1; } else if(hdr.magic == 0xcffaedfe) { conv = 1; m64 = 1; } else { cli_dbgmsg("cli_scanmacho: Incorrect magic\n"); return matcher ? -1 : CL_EFORMAT; } switch(EC32(hdr.cpu_type, conv)) { case 7: if(!matcher) cli_dbgmsg("MACHO: CPU Type: Intel 32-bit\n"); arch = 1; break; case 7 | 0x1000000: if(!matcher) cli_dbgmsg("MACHO: CPU Type: Intel 64-bit\n"); break; case 12: if(!matcher) cli_dbgmsg("MACHO: CPU Type: ARM\n"); break; case 14: if(!matcher) cli_dbgmsg("MACHO: CPU Type: SPARC\n"); break; case 18: if(!matcher) cli_dbgmsg("MACHO: CPU Type: POWERPC 32-bit\n"); arch = 2; break; case 18 | 0x1000000: if(!matcher) cli_dbgmsg("MACHO: CPU Type: POWERPC 64-bit\n"); arch = 3; break; default: if(!matcher) cli_dbgmsg("MACHO: CPU Type: ** UNKNOWN ** (%u)\n", EC32(hdr.cpu_type, conv)); break; } if(!matcher) switch(EC32(hdr.filetype, conv)) { case 0x1: /* MH_OBJECT */ cli_dbgmsg("MACHO: Filetype: Relocatable object file\n"); break; case 0x2: /* MH_EXECUTE */ cli_dbgmsg("MACHO: Filetype: Executable\n"); break; case 0x3: /* MH_FVMLIB */ cli_dbgmsg("MACHO: Filetype: Fixed VM shared library file\n"); break; case 0x4: /* MH_CORE */ cli_dbgmsg("MACHO: Filetype: Core file\n"); break; case 0x5: /* MH_PRELOAD */ cli_dbgmsg("MACHO: Filetype: Preloaded executable file\n"); break; case 0x6: /* MH_DYLIB */ cli_dbgmsg("MACHO: Filetype: Dynamically bound shared library\n"); break; case 0x7: /* MH_DYLINKER */ cli_dbgmsg("MACHO: Filetype: Dynamic link editor\n"); break; case 0x8: /* MH_BUNDLE */ cli_dbgmsg("MACHO: Filetype: Dynamically bound bundle file\n"); break; case 0x9: /* MH_DYLIB_STUB */ cli_dbgmsg("MACHO: Filetype: Shared library stub for static\n"); break; default: cli_dbgmsg("MACHO: Filetype: ** UNKNOWN ** (0x%x)\n", EC32(hdr.filetype, conv)); } if(!matcher) { cli_dbgmsg("MACHO: Number of load commands: %u\n", EC32(hdr.ncmds, conv)); cli_dbgmsg("MACHO: Size of load commands: %u\n", EC32(hdr.sizeofcmds, conv)); } if(m64) at += 4; hdr.ncmds = EC32(hdr.ncmds, conv); if(!hdr.ncmds || hdr.ncmds > 1024) { cli_dbgmsg("cli_scanmacho: Invalid number of load commands (%u)\n", hdr.ncmds); RETURN_BROKEN; } for(i = 0; i < hdr.ncmds; i++) { if(fmap_readn(map, &load_cmd, at, sizeof(load_cmd)) != sizeof(load_cmd)) { cli_dbgmsg("cli_scanmacho: Can't read load command\n"); free(sections); RETURN_BROKEN; } at += sizeof(load_cmd); /* if((m64 && EC32(load_cmd.cmdsize, conv) % 8) || (!m64 && EC32(load_cmd.cmdsize, conv) % 4)) { cli_dbgmsg("cli_scanmacho: Invalid command size (%u)\n", EC32(load_cmd.cmdsize, conv)); free(sections); RETURN_BROKEN; } */ load_cmd.cmd = EC32(load_cmd.cmd, conv); if((m64 && load_cmd.cmd == 0x19) || (!m64 && load_cmd.cmd == 0x01)) { /* LC_SEGMENT */ if(m64) { if(fmap_readn(map, &segment_cmd64, at, sizeof(segment_cmd64)) != sizeof(segment_cmd64)) { cli_dbgmsg("cli_scanmacho: Can't read segment command\n"); free(sections); RETURN_BROKEN; } at += sizeof(segment_cmd64); nsects = EC32(segment_cmd64.nsects, conv); strncpy(name, segment_cmd64.segname, sizeof(name)); name[sizeof(name)-1] = '\0'; } else { if(fmap_readn(map, &segment_cmd, at, sizeof(segment_cmd)) != sizeof(segment_cmd)) { cli_dbgmsg("cli_scanmacho: Can't read segment command\n"); free(sections); RETURN_BROKEN; } at += sizeof(segment_cmd); nsects = EC32(segment_cmd.nsects, conv); strncpy(name, segment_cmd.segname, sizeof(name)); } if(!matcher) { name[sizeof(name)-1] = '\0'; cli_dbgmsg("MACHO: Segment name: %s\n", name); cli_dbgmsg("MACHO: Number of sections: %u\n", nsects); } if(nsects > 255) { cli_dbgmsg("cli_scanmacho: Invalid number of sections\n"); free(sections); RETURN_BROKEN; } if(!nsects) { if(!matcher) cli_dbgmsg("MACHO: ------------------\n"); continue; } sections = (struct cli_exe_section *) cli_realloc2(sections, (sect + nsects) * sizeof(struct cli_exe_section)); if(!sections) { cli_errmsg("cli_scanmacho: Can't allocate memory for 'sections'\n"); return matcher ? -1 : CL_EMEM; } for(j = 0; j < nsects; j++) { if(m64) { if(fmap_readn(map, §ion64, at, sizeof(section64)) != sizeof(section64)) { cli_dbgmsg("cli_scanmacho: Can't read section\n"); free(sections); RETURN_BROKEN; } at += sizeof(section64); sections[sect].rva = EC64(section64.addr, conv); sections[sect].vsz = EC64(section64.size, conv); sections[sect].raw = EC32(section64.offset, conv); section64.align = 1 << EC32(section64.align, conv); sections[sect].rsz = sections[sect].vsz + (section64.align - (sections[sect].vsz % section64.align)) % section64.align; /* most likely we can assume it's the same as .vsz */ strncpy(name, section64.sectname, sizeof(name)); } else { if(fmap_readn(map, §ion, at, sizeof(section)) != sizeof(section)) { cli_dbgmsg("cli_scanmacho: Can't read section\n"); free(sections); RETURN_BROKEN; } at += sizeof(section); sections[sect].rva = EC32(section.addr, conv); sections[sect].vsz = EC32(section.size, conv); sections[sect].raw = EC32(section.offset, conv); section.align = 1 << EC32(section.align, conv); sections[sect].rsz = sections[sect].vsz + (section.align - (sections[sect].vsz % section.align)) % section.align; strncpy(name, section.sectname, sizeof(name)); } if(!matcher) { name[sizeof(name)-1] = '\0'; cli_dbgmsg("MACHO: --- Section %u ---\n", sect); cli_dbgmsg("MACHO: Name: %s\n", name); cli_dbgmsg("MACHO: Virtual address: 0x%x\n", (unsigned int) sections[sect].rva); cli_dbgmsg("MACHO: Virtual size: %u\n", (unsigned int) sections[sect].vsz); cli_dbgmsg("MACHO: Raw size: %u\n", (unsigned int) sections[sect].rsz); if(sections[sect].raw) cli_dbgmsg("MACHO: File offset: %u\n", (unsigned int) sections[sect].raw); } sect++; } if(!matcher) cli_dbgmsg("MACHO: ------------------\n"); } else if(arch && (load_cmd.cmd == 0x4 || load_cmd.cmd == 0x5)) { /* LC_(UNIX)THREAD */ at += 8; switch(arch) { case 1: /* x86 */ { struct macho_thread_state_x86 thread_state_x86; if(fmap_readn(map, &thread_state_x86, at, sizeof(thread_state_x86)) != sizeof(thread_state_x86)) { cli_dbgmsg("cli_scanmacho: Can't read thread_state_x86\n"); free(sections); RETURN_BROKEN; } at += sizeof(thread_state_x86); break; } case 2: /* PPC */ { struct macho_thread_state_ppc thread_state_ppc; if(fmap_readn(map, &thread_state_ppc, at, sizeof(thread_state_ppc)) != sizeof(thread_state_ppc)) { cli_dbgmsg("cli_scanmacho: Can't read thread_state_ppc\n"); free(sections); RETURN_BROKEN; } at += sizeof(thread_state_ppc); ep = EC32(thread_state_ppc.srr0, conv); break; } case 3: /* PPC64 */ { struct macho_thread_state_ppc64 thread_state_ppc64; if(fmap_readn(map, &thread_state_ppc64, at, sizeof(thread_state_ppc64)) != sizeof(thread_state_ppc64)) { cli_dbgmsg("cli_scanmacho: Can't read thread_state_ppc64\n"); free(sections); RETURN_BROKEN; } at += sizeof(thread_state_ppc64); ep = EC64(thread_state_ppc64.srr0, conv); break; } default: cli_errmsg("cli_scanmacho: Invalid arch setting!\n"); free(sections); return matcher ? -1 : CL_EARG; } } else { if(EC32(load_cmd.cmdsize, conv) > sizeof(load_cmd)) at += EC32(load_cmd.cmdsize, conv) - sizeof(load_cmd); } } if(ep) { if(!matcher) cli_dbgmsg("Entry Point: 0x%x\n", ep); if(sections) { ep = cli_rawaddr(ep, sections, sect, &err); if(err) { cli_dbgmsg("cli_scanmacho: Can't calculate EP offset\n"); free(sections); return matcher ? -1 : CL_EFORMAT; } if(!matcher) cli_dbgmsg("Entry Point file offset: %u\n", ep); } } if(matcher) { fileinfo->ep = ep; fileinfo->nsections = sect; fileinfo->section = sections; return 0; } else { free(sections); return CL_SUCCESS; } }