diff --git a/README.md b/README.md index f53f11d..7d6072a 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,48 @@ -# todo +# helpelf: an open and lightweight ELF recon tool -- display e_type and rest of header for 32/64 -- display protections -- colorful -- maybe display elf sections if command entered (??) +helpelf aims to be a replacement to the `file` and `checksec` utilities that are used by many hackers. The tool does not rely on readelf like `checksec` does, but instead includes its own ELF parser. Works for both 32 and 64-bit versions of the standard. -- security: -enforce malloc secure fail +## Installation + +This will install `helpelf` and the alias `he` on the command line. No dependencies are required besides the C toolchain & make utility. + +``` +make +sudo make install +``` + +## Usage + +The output is colorful (not visible here). + +``` +$ helpelf +Usage: helpelf [options] + -v --verbose displays more information +$ he a.out +a.out: 64-bit ELF (little-endian) amd64, NX enabled, PIE disabled, Stack canary enabled, Partial RELRO, Not stripped +$ he a.out -v +a.out: 64-bit ELF (little-endian) version 1 (executable file), amd64, statically linked, NX enabled, PIE disabled, Stack canary enabled, Partial RELRO, Not stripped +``` + +## Documentation + +The tool was developed according to the ELF 32 and 64 bit standards that can be found here: + +- [ELF-64 Object File Format](https://uclibc.org/docs/elf-64-gen.pdf) +- [Tool Interface Standard (TIS) Executable and Linking Format (ELF) Specification](https://refspecs.linuxfoundation.org/elf/elf.pdf) + +## License + +This project is under the Unlicense (part of public domain). + +## TODO + +- optimization: find a workaround for 32/64 sec differentiation +- verbose: display detailed header info +- verbose: display elf sections + +## Known issues + +- the RELRO check gives false-negatives on Full RELRO (instead shows Partial-RELRO) +- any contribution and/or feedback is highly appreciated. Open an issue/pull request if you'd like to add something. diff --git a/main.c b/main.c index 3e5eab2..c53792e 100644 --- a/main.c +++ b/main.c @@ -468,6 +468,189 @@ void check_sec32(Elf32_Ehdr* header, FILE* fp) free(p_headers); } +// Duplicate code. Should find a workaround +void check_sec64(Elf64_Ehdr* header, FILE* fp) +{ + /* NX: + * Browse thru program header table (e_phnum items) from e_phoff (program header offset) + * if its stack and has executable bit then we know NX isnt there + */ + fseek(fp, header->e_phoff, SEEK_SET); + for (size_t i=0; ie_phnum; i++) + { + Elf64_Phdr p_header; + if (!fread(&p_header, 1, sizeof(Elf64_Phdr), fp)) + { + puts("Read error."); + exit(-EIO); + } + if (p_header.p_type == PT_GNU_STACK) + { + if (p_header.p_flags & PF_X) + { + nx = false; + } + else + { + nx = true; + } + } + } + + /* Stack canary: + * Same for section header table (e_shnum items) from e_shoff (section header offset) + * find sections .dynsym or .symtab: + * and look for __stack_chk_fail (call for stack smashing) + */ + fseek(fp, header->e_shoff, SEEK_SET); + + // Read section headers + Elf64_Shdr *s_headers = malloc(header->e_shnum * sizeof(Elf64_Shdr)); + if (!s_headers) + { + puts("Memory allocation error."); + exit(-ENOMEM); + } + if (!fread(s_headers, sizeof(Elf64_Shdr), header->e_shnum, fp)) + { + puts("Read error."); + exit(-EIO); + } + + // Find section name string table (at e_shstrndx) and read it + Elf64_Shdr s_header_str = s_headers[header->e_shstrndx]; + char* s_header_strtab = malloc(s_header_str.sh_size); + if (!s_header_strtab) + { + puts("Memory allocation error."); + exit(-ENOMEM); + } + fseek(fp, s_header_str.sh_offset, SEEK_SET); + if (!fread(s_header_strtab, 1, s_header_str.sh_size, fp)) + { + puts("Read error."); + exit(-EIO); + } + + for (size_t i=0; ie_shnum; i++) + { + // additional small check for stripping as we're already browsing section headers + if (s_headers[i].sh_type == SHT_SYMTAB && s_headers[i].sh_size > 0) stripped = false; + + if (s_headers[i].sh_type == SHT_DYNSYM || s_headers[i].sh_type == SHT_SYMTAB) + { + // load symbol table + unsigned int nsyms = s_headers[i].sh_size / s_headers[i].sh_entsize; + Elf64_Sym *syms = malloc(s_headers[i].sh_size); + if (!syms) + { + puts("Memory allocation error."); + exit(-EIO); + } + fseek(fp, s_headers[i].sh_offset, SEEK_SET); + if (!fread(syms, s_headers[i].sh_size, 1, fp)) + { + puts("Read error."); + exit(-EIO); + } + + // load string table + Elf64_Shdr str_header = s_headers[s_headers[i].sh_link]; + char* strtab = malloc(str_header.sh_size); + if (!strtab) + { + puts("Memory allocation error."); + exit(-ENOMEM); + } + fseek(fp, str_header.sh_offset, SEEK_SET); + if (!fread(strtab, 1, str_header.sh_size, fp)) + { + puts("Read error."); + exit(-EIO); + } + + // iterate through symbols, check for __stack_chk_fail + for (size_t j=0; je_phoff, SEEK_SET); + Elf64_Phdr *p_headers = malloc(header->e_phnum * sizeof(Elf64_Phdr)); + if (!p_headers) + { + puts("Memory allocation error."); + exit(-ENOMEM); + } + if (!fread (p_headers, sizeof(Elf64_Phdr), header->e_phnum, fp)) + { + puts("Read error."); + exit(-EIO); + } + + Elf64_Off dyn_off = 0; + Elf64_Word dyn_size = 0; + + for (int i=0; ie_phnum; i++) + { + if (p_headers[i].p_type == PT_GNU_RELRO) + { + relro = true; + } + if (p_headers[i].p_type == PT_DYNAMIC) + { + dyn_off = p_headers[i].p_offset; + dyn_size = p_headers[i].p_filesz; + } + } + + if (dyn_off && dyn_size) + { + int n = dyn_size / sizeof(Elf64_Dyn); + Elf64_Dyn *dyns = malloc(dyn_size); + if (!dyns) + { + puts("Memory allocation error."); + exit(-ENOMEM); + } + fseek(fp, dyn_off, SEEK_SET); + if (!fread(dyns, sizeof(Elf64_Dyn), n, fp)) + { + puts("Read error."); + exit(-EIO); + } + + for (int i=0; i