/** * @author n1073645 [n1073645@gmail.com] * @copyright Crown Copyright 2022 * @license Apache-2.0 */ import Operation from "../Operation.mjs"; import Stream from "../lib/Stream.mjs"; import Utils from "../Utils.mjs"; import OperationError from "../errors/OperationError.mjs"; /** * ELF Info operation */ class ELFInfo extends Operation { /** * ELFInfo constructor */ constructor() { super(); this.name = "ELF Info"; this.module = "Default"; this.description = "Implements readelf-like functionality. This operation will extract the ELF Header, Program Headers, Section Headers and Symbol Table for an ELF file."; this.infoURL = "https://www.wikipedia.org/wiki/Executable_and_Linkable_Format"; this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = []; } /** * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ run(input, args) { let phoff = 0; let phEntries = 0; let shoff = 0; let shEntries = 0; let shentSize = 0; let entry = 0; let format = 0; let endianness = ""; let shstrtab = 0; let namesOffset = 0; let symtabOffset = 0; let symtabSize = 0; let symtabEntSize = 0; let strtabOffset = 0; const align = 30; /** * This function reads characters until it hits a null terminator. * * @param {stream} stream * @param {integer} namesOffset * @param {integer} nameOffset * @returns {string} */ function readString(stream, namesOffset, nameOffset) { const preMove = stream.position; stream.moveTo(namesOffset + nameOffset); const nameResult = stream.readString(); stream.moveTo(preMove); return nameResult; } /** * This function parses and extracts relevant information from the ELF Header. * * @param {stream} stream * @returns {string} */ function elfHeader(stream) { /** * The ELF Header is comprised of the following structures depending on the binary's format. * * e_ident - The Magic Number 0x7F,0x45,0x4c,0x46 * - Byte set to 1 or 2 to signify 32-bit or 64-bit format, respectively. * - Byte set to 1 or 2 to signify little of big endianness, respectively. * - Byte set to 1 for the version of ELF. * - Byte identifying the target OS ABI. * - Byte further identifying the OS ABI Version. * - 7 Padding Bytes. * e_type - 2 bytes identifying the object file type. * e_machine - 2 bytes identifying the instruction set architecture. * e_version - Byte set to 1 for the version of ELF. * * 32-bit: * e_entry - 4 Bytes specifying the entry point. * e_phoff - 4 Bytes specifying the offset of the Program Header Table. * e_shoff - 4 Bytes specifying the offset of the Section Header Table. * * 64-bit: * e_entry - 8 Bytes specifying the entry point. * e_phoff - 8 Bytes specifying the offset of the Program Header Table. * e_shoff - 8 Bytes specifying the offset of the Section Header Table. * * e_flags - 4 Bytes specifying processor specific flags. * e_ehsize - 2 Bytes specifying the size of the ELF Header. * e_phentsize - 2 Bytes specifying the size of a Program Header Table Entry. * e_phnum - 2 Bytes specifying the number of entries in the Program Header Table. * e_shentsize - 2 Bytes specifying the size of a Section Header Table Entry. * e_shnum - 2 Bytes specifying the number of entries in the Section Header Table. * e_shstrndx - 2 Bytes specifying the index of the section containing the section names in the Section Header Table. */ const ehResult = []; const magic = stream.getBytes(4); if (magic.join("") !== [0x7f, 0x45, 0x4c, 0x46].join("")) throw new OperationError("Invalid ELF"); ehResult.push("Magic:".padEnd(align) + `${Utils.byteArrayToChars(magic)}`); format = stream.readInt(1); ehResult.push("Format:".padEnd(align) + `${format === 1 ? "32-bit" : "64-bit"}`); endianness = stream.readInt(1) === 1 ? "le" : "be"; ehResult.push("Endianness:".padEnd(align) + `${endianness === "le" ? "Little" : "Big"}`); ehResult.push("Version:".padEnd(align) + `${stream.readInt(1).toString()}`); let ABI = ""; switch (stream.readInt(1)) { case 0x00: ABI = "System V"; break; case 0x01: ABI = "HP-UX"; break; case 0x02: ABI = "NetBSD"; break; case 0x03: ABI = "Linux"; break; case 0x04: ABI = "GNU Hurd"; break; case 0x06: ABI = "Solaris"; break; case 0x07: ABI = "AIX"; break; case 0x08: ABI = "IRIX"; break; case 0x09: ABI = "FreeBSD"; break; case 0x0A: ABI = "Tru64"; break; case 0x0B: ABI = "Novell Modesto"; break; case 0x0C: ABI = "OpenBSD"; break; case 0x0D: ABI = "OpenVMS"; break; case 0x0E: ABI = "NonStop Kernel"; break; case 0x0F: ABI = "AROS"; break; case 0x10: ABI = "Fenix OS"; break; case 0x11: ABI = "CloudABI"; break; case 0x12: ABI = "Stratus Technologies OpenVOS"; break; default: break; } ehResult.push("ABI:".padEnd(align) + ABI); // Linux Kernel does not use ABI Version. const abiVersion = stream.readInt(1).toString(); if (ABI !== "Linux") ehResult.push("ABI Version:".padEnd(align) + abiVersion); stream.moveForwardsBy(7); let eType = ""; switch (stream.readInt(2, endianness)) { case 0x0000: eType = "Unknown"; break; case 0x0001: eType = "Relocatable File"; break; case 0x0002: eType = "Executable File"; break; case 0x0003: eType = "Shared Object"; break; case 0x0004: eType = "Core File"; break; case 0xFE00: eType = "LOOS"; break; case 0xFEFF: eType = "HIOS"; break; case 0xFF00: eType = "LOPROC"; break; case 0xFFFF: eType = "HIPROC"; break; default: break; } ehResult.push("Type:".padEnd(align) + eType); let ISA = ""; switch (stream.readInt(2, endianness)) { case 0x0000: ISA = "No specific instruction set"; break; case 0x0001: ISA = "AT&T WE 32100"; break; case 0x0002: ISA = "SPARC"; break; case 0x0003: ISA = "x86"; break; case 0x0004: ISA = "Motorola 68000 (M68k)"; break; case 0x0005: ISA = "Motorola 88000 (M88k)"; break; case 0x0006: ISA = "Intel MCU"; break; case 0x0007: ISA = "Intel 80860"; break; case 0x0008: ISA = "MIPS"; break; case 0x0009: ISA = "IBM System/370"; break; case 0x000A: ISA = "MIPS RS3000 Little-endian"; break; case 0x000B: case 0x000C: case 0x000D: case 0x000E: case 0x0018: case 0x0019: case 0x001A: case 0x001B: case 0x001C: case 0x001D: case 0x001E: case 0x001F: case 0x0020: case 0x0021: case 0x0022: case 0x0023: ISA = "Reserved for future use"; break; case 0x000F: ISA = "Hewlett-Packard PA-RISC"; break; case 0x0011: ISA = "Fujitsu VPP500"; break; case 0x0012: ISA = "Enhanced instruction set SPARC"; break; case 0x0013: ISA = "Intel 80960"; break; case 0x0014: ISA = "PowerPC"; break; case 0x0015: ISA = "PowerPC (64-bit)"; break; case 0x0016: ISA = "S390, including S390"; break; case 0x0017: ISA = "IBM SPU/SPC"; break; case 0x0024: ISA = "NEC V800"; break; case 0x0025: ISA = "Fujitsu FR20"; break; case 0x0026: ISA = "TRW RH-32"; break; case 0x0027: ISA = "Motorola RCE"; break; case 0x0028: ISA = "ARM (up to ARMv7/Aarch32)"; break; case 0x0029: ISA = "Digital Alpha"; break; case 0x002A: ISA = "SuperH"; break; case 0x002B: ISA = "SPARC Version 9"; break; case 0x002C: ISA = "Siemens TriCore embedded processor"; break; case 0x002D: ISA = "Argonaut RISC Core"; break; case 0x002E: ISA = "Hitachi H8/300"; break; case 0x002F: ISA = "Hitachi H8/300H"; break; case 0x0030: ISA = "Hitachi H8S"; break; case 0x0031: ISA = "Hitachi H8/500"; break; case 0x0032: ISA = "IA-64"; break; case 0x0033: ISA = "Standford MIPS-X"; break; case 0x0034: ISA = "Motorola ColdFire"; break; case 0x0035: ISA = "Motorola M68HC12"; break; case 0x0036: ISA = "Fujitsu MMA Multimedia Accelerator"; break; case 0x0037: ISA = "Siemens PCP"; break; case 0x0038: ISA = "Sony nCPU embedded RISC processor"; break; case 0x0039: ISA = "Denso NDR1 microprocessor"; break; case 0x003A: ISA = "Motorola Star*Core processor"; break; case 0x003B: ISA = "Toyota ME16 processor"; break; case 0x003C: ISA = "STMicroelectronics ST100 processor"; break; case 0x003D: ISA = "Advanced Logic Corp. TinyJ embedded processor family"; break; case 0x003E: ISA = "AMD x86-64"; break; case 0x003F: ISA = "Sony DSP Processor"; break; case 0x0040: ISA = "Digital Equipment Corp. PDP-10"; break; case 0x0041: ISA = "Digital Equipment Corp. PDP-11"; break; case 0x0042: ISA = "Siemens FX66 microcontroller"; break; case 0x0043: ISA = "STMicroelectronics ST9+ 8/16 bit microcontroller"; break; case 0x0044: ISA = "STMicroelectronics ST7 8-bit microcontroller"; break; case 0x0045: ISA = "Motorola MC68HC16 Microcontroller"; break; case 0x0046: ISA = "Motorola MC68HC11 Microcontroller"; break; case 0x0047: ISA = "Motorola MC68HC08 Microcontroller"; break; case 0x0048: ISA = "Motorola MC68HC05 Microcontroller"; break; case 0x0049: ISA = "Silicon Graphics SVx"; break; case 0x004A: ISA = "STMicroelectronics ST19 8-bit microcontroller"; break; case 0x004B: ISA = "Digital VAX"; break; case 0x004C: ISA = "Axis Communications 32-bit embedded processor"; break; case 0x004D: ISA = "Infineon Technologies 32-bit embedded processor"; break; case 0x004E: ISA = "Element 14 64-bit DSP Processor"; break; case 0x004F: ISA = "LSI Logic 16-bit DSP Processor"; break; case 0x0050: ISA = "Donald Knuth's educational 64-bit processor"; break; case 0x0051: ISA = "Harvard University machine-independent object files"; break; case 0x0052: ISA = "SiTera Prism"; break; case 0x0053: ISA = "Atmel AVR 8-bit microcontroller"; break; case 0x0054: ISA = "Fujitsu FR30"; break; case 0x0055: ISA = "Mitsubishi D10V"; break; case 0x0056: ISA = "Mitsubishi D30V"; break; case 0x0057: ISA = "NEC v850"; break; case 0x0058: ISA = "Mitsubishi M32R"; break; case 0x0059: ISA = "Matsushita MN10300"; break; case 0x005A: ISA = "Matsushita MN10200"; break; case 0x005B: ISA = "picoJava"; break; case 0x005C: ISA = "OpenRISC 32-bit embedded processor"; break; case 0x005D: ISA = "ARC Cores Tangent-A5"; break; case 0x005E: ISA = "Tensilica Xtensa Architecture"; break; case 0x005F: ISA = "Alphamosaic VideoCore processor"; break; case 0x0060: ISA = "Thompson Multimedia General Purpose Processor"; break; case 0x0061: ISA = "National Semiconductor 32000 series"; break; case 0x0062: ISA = "Tenor Network TPC processor"; break; case 0x0063: ISA = "Trebia SNP 1000 processor"; break; case 0x0064: ISA = "STMicroelectronics (www.st.com) ST200 microcontroller"; break; case 0x008C: ISA = "TMS320C6000 Family"; break; case 0x00AF: ISA = "MCST Elbrus e2k"; break; case 0x00B7: ISA = "ARM 64-bits (ARMv8/Aarch64)"; break; case 0x00F3: ISA = "RISC-V"; break; case 0x00F7: ISA = "Berkeley Packet Filter"; break; case 0x0101: ISA = "WDC 65C816"; break; default: ISA = "Unimplemented"; break; } ehResult.push("Instruction Set Architecture:".padEnd(align) + ISA); ehResult.push("ELF Version:".padEnd(align) + `${stream.readInt(4, endianness)}`); const readSize = format === 1 ? 4 : 8; entry = stream.readInt(readSize, endianness); phoff = stream.readInt(readSize, endianness); shoff = stream.readInt(readSize, endianness); ehResult.push("Entry Point:".padEnd(align) + `0x${Utils.hex(entry)}`); ehResult.push("Entry PHOFF:".padEnd(align) + `0x${Utils.hex(phoff)}`); ehResult.push("Entry SHOFF:".padEnd(align) + `0x${Utils.hex(shoff)}`); const flags = stream.readInt(4, endianness); ehResult.push("Flags:".padEnd(align) + `${Utils.bin(flags)}`); ehResult.push("ELF Header Size:".padEnd(align) + `${stream.readInt(2, endianness)} bytes`); ehResult.push("Program Header Size:".padEnd(align) + `${stream.readInt(2, endianness)} bytes`); phEntries = stream.readInt(2, endianness); ehResult.push("Program Header Entries:".padEnd(align) + phEntries); shentSize = stream.readInt(2, endianness); ehResult.push("Section Header Size:".padEnd(align) + shentSize + " bytes"); shEntries = stream.readInt(2, endianness); ehResult.push("Section Header Entries:".padEnd(align) + shEntries); shstrtab = stream.readInt(2, endianness); ehResult.push("Section Header Names:".padEnd(align) + shstrtab); return ehResult.join("\n"); } /** * This function parses and extracts relevant information from a Program Header. * * @param {stream} stream * @returns {string} */ function programHeader(stream) { /** * A Program Header is comprised of the following structures depending on the binary's format. * * p_type - 4 Bytes identifying the type of the segment. * * 32-bit: * p_offset - 4 Bytes specifying the offset of the segment. * p_vaddr - 4 Bytes specifying the virtual address of the segment in memory. * p_paddr - 4 Bytes specifying the physical address of the segment in memory. * p_filesz - 4 Bytes specifying the size in bytes of the segment in the file image. * p_memsz - 4 Bytes specifying the size in bytes of the segment in memory. * p_flags - 4 Bytes identifying the segment dependent flags. * p_align - 4 Bytes set to 0 or 1 for alignment or no alignment, respectively. * * 64-bit: * p_flags - 4 Bytes identifying segment dependent flags. * p_offset - 8 Bytes specifying the offset of the segment. * p_vaddr - 8 Bytes specifying the virtual address of the segment in memory. * p_paddr - 8 Bytes specifying the physical address of the segment in memory. * p_filesz - 8 Bytes specifying the size in bytes of the segment in the file image. * p_memsz - 8 Bytes specifying the size in bytes of the segment in memory. * p_align - 8 Bytes set to 0 or 1 for alignment or no alignment, respectively. */ /** * This function decodes the flags bitmask for the Program Header. * * @param {integer} flags * @returns {string} */ function readFlags(flags) { const result = []; if (flags & 0x1) result.push("Execute"); if (flags & 0x2) result.push("Write"); if (flags & 0x4) result.push("Read"); if (flags & 0xf0000000) result.push("Unspecified"); return result.join(","); } const phResult = []; let pType = ""; const programHeaderType = stream.readInt(4, endianness); switch (true) { case (programHeaderType === 0x00000000): pType = "Unused"; break; case (programHeaderType === 0x00000001): pType = "Loadable Segment"; break; case (programHeaderType === 0x00000002): pType = "Dynamic linking information"; break; case (programHeaderType === 0x00000003): pType = "Interpreter Information"; break; case (programHeaderType === 0x00000004): pType = "Auxiliary Information"; break; case (programHeaderType === 0x00000005): pType = "Reserved"; break; case (programHeaderType === 0x00000006): pType = "Program Header Table"; break; case (programHeaderType === 0x00000007): pType = "Thread-Local Storage Template"; break; case (programHeaderType >= 0x60000000 && programHeaderType <= 0x6FFFFFFF): pType = "Reserved Inclusive Range. OS Specific"; break; case (programHeaderType >= 0x70000000 && programHeaderType <= 0x7FFFFFFF): pType = "Reserved Inclusive Range. Processor Specific"; break; default: break; } phResult.push("Program Header Type:".padEnd(align) + pType); if (format === 2) phResult.push("Flags:".padEnd(align) + readFlags(stream.readInt(4, endianness))); const readSize = format === 1? 4 : 8; phResult.push("Offset Of Segment:".padEnd(align) + `${stream.readInt(readSize, endianness)}`); phResult.push("Virtual Address of Segment:".padEnd(align) + `${stream.readInt(readSize, endianness)}`); phResult.push("Physical Address of Segment:".padEnd(align) + `${stream.readInt(readSize, endianness)}`); phResult.push("Size of Segment:".padEnd(align) + `${stream.readInt(readSize, endianness)} bytes`); phResult.push("Size of Segment in Memory:".padEnd(align) + `${stream.readInt(readSize, endianness)} bytes`); if (format === 1) phResult.push("Flags:".padEnd(align) + readFlags(stream.readInt(4, endianness))); stream.moveForwardsBy(readSize); return phResult.join("\n"); } /** * This function parses and extracts relevant information from a Section Header. * * @param {stream} stream * @returns {string} */ function sectionHeader(stream) { /** * A Section Header is comprised of the following structures depending on the binary's format. * * sh_name - 4 Bytes identifying the offset into the .shstrtab for the name of this section. * sh_type - 4 Bytes identifying the type of this header. * * 32-bit: * sh_flags - 4 Bytes identifying section specific flags. * sh_addr - 4 Bytes identifying the virtual address of the section in memory. * sh_offset - 4 Bytes identifying the offset of the section in the file. * sh_size - 4 Bytes specifying the size in bytes of the section in the file image. * sh_link - 4 Bytes identifying the index of an associated section. * sh_info - 4 Bytes specifying extra information about the section. * sh_addralign - 4 Bytes containing the alignment for the section. * sh_entsize - 4 Bytes specifying the size, in bytes, of each entry in the section. * * 64-bit: * sh_flags - 8 Bytes identifying section specific flags. * sh_addr - 8 Bytes identifying the virtual address of the section in memory. * sh_offset - 8 Bytes identifying the offset of the section in the file. * sh_size - 8 Bytes specifying the size in bytes of the section in the file image. * sh_link - 4 Bytes identifying the index of an associated section. * sh_info - 4 Bytes specifying extra information about the section. * sh_addralign - 8 Bytes containing the alignment for the section. * sh_entsize - 8 Bytes specifying the size, in bytes, of each entry in the section. */ const shResult = []; const nameOffset = stream.readInt(4, endianness); let type = ""; const shType = stream.readInt(4, endianness); switch (true) { case (shType === 0x00000001): type = "Program Data"; break; case (shType === 0x00000002): type = "Symbol Table"; break; case (shType === 0x00000003): type = "String Table"; break; case (shType === 0x00000004): type = "Relocation Entries with Addens"; break; case (shType === 0x00000005): type = "Symbol Hash Table"; break; case (shType === 0x00000006): type = "Dynamic Linking Information"; break; case (shType === 0x00000007): type = "Notes"; break; case (shType === 0x00000008): type = "Program Space with No Data"; break; case (shType === 0x00000009): type = "Relocation Entries with no Addens"; break; case (shType === 0x0000000A): type = "Reserved"; break; case (shType === 0x0000000B): type = "Dynamic Linker Symbol Table"; break; case (shType === 0x0000000E): type = "Array of Constructors"; break; case (shType === 0x0000000F): type = "Array of Destructors"; break; case (shType === 0x00000010): type = "Array of pre-constructors"; break; case (shType === 0x00000011): type = "Section group"; break; case (shType === 0x00000012): type = "Extended section indices"; break; case (shType === 0x00000013): type = "Number of defined types"; break; case (shType >= 0x60000000 && shType <= 0x6fffffff): type = "OS-specific"; break; case (shType >= 0x70000000 && shType <= 0x7fffffff): type = "Processor-specific"; break; case (shType >= 0x80000000 && shType <= 0x8fffffff): type = "Application-specific"; break; default: type = "Unused"; break; } shResult.push("Type:".padEnd(align) + type); let nameResult = ""; if (type !== "Unused") { nameResult = readString(stream, namesOffset, nameOffset); shResult.push("Section Name: ".padEnd(align) + nameResult); } const readSize = (format === 1) ? 4 : 8; const flags = stream.readInt(readSize, endianness); const shFlags = []; const bitMasks = [ [0x00000001, "Writable"], [0x00000002, "Alloc"], [0x00000004, "Executable"], [0x00000010, "Merge"], [0x00000020, "Strings"], [0x00000040, "SHT Info Link"], [0x00000080, "Link Order"], [0x00000100, "OS Specific Handling"], [0x00000200, "Group"], [0x00000400, "Thread Local Data"], [0x0FF00000, "OS-Specific"], [0xF0000000, "Processor Specific"], [0x04000000, "Special Ordering (Solaris)"], [0x08000000, "Excluded (Solaris)"] ]; bitMasks.forEach(elem => { if (flags & elem[0]) shFlags.push(elem[1]); }); shResult.push("Flags:".padEnd(align) + shFlags); const vaddr = stream.readInt(readSize, endianness); shResult.push("Section Vaddr in memory:".padEnd(align) + vaddr); const shoffset = stream.readInt(readSize, endianness); shResult.push("Offset of the section:".padEnd(align) + shoffset); const secSize = stream.readInt(readSize, endianness); shResult.push("Section Size:".padEnd(align) + secSize); const associatedSection = stream.readInt(4, endianness); shResult.push("Associated Section:".padEnd(align) + associatedSection); const extraInfo = stream.readInt(4, endianness); shResult.push("Section Extra Information:".padEnd(align) + extraInfo); // Jump over alignment field. stream.moveForwardsBy(readSize); const entSize = stream.readInt(readSize, endianness); switch (nameResult) { case ".strtab": strtabOffset = shoffset; break; case ".symtab": symtabOffset = shoffset; symtabSize = secSize; symtabEntSize = entSize; break; default: break; } return shResult.join("\n"); } /** * This function returns the offset of the Section Header Names Section. * * @param {stream} stream */ function getNamesOffset(stream) { const preMove = stream.position; stream.moveTo(shoff + (shentSize * shstrtab)); if (format === 1) { stream.moveForwardsBy(0x10); namesOffset = stream.readInt(4, endianness); } else { stream.moveForwardsBy(0x18); namesOffset = stream.readInt(8, endianness); } stream.position = preMove; } /** * This function returns a symbol's name from the string table. * * @param {stream} stream * @returns {string} */ function getSymbols(stream) { /** * The Symbol Table is comprised of Symbol Table Entries whose structure depends on the binary's format. * * 32-bit: * st_name - 4 Bytes specifying an index in the files symbol string table. * st_value - 4 Bytes identifying the value associated with the symbol. * st_size - 4 Bytes specifying the size associated with the symbol (this is not the size of the symbol). * st_info - A byte specifying the type and binding of the symbol. * st_other - A byte specifying the symbol's visibility. * st_shndx - 2 Bytes identifying the section that this symbol is related to. * * 64-bit: * st_name - 4 Bytes specifying an index in the files symbol string table. * st_info - A byte specifying the type and binding of the symbol. * st_other - A byte specifying the symbol's visibility. * st_shndx - 2 Bytes identifying the section that this symbol is related to. * st_value - 8 Bytes identifying the value associated with the symbol. * st_size - 8 Bytes specifying the size associated with the symbol (this is not the size of the symbol). */ const nameOffset = stream.readInt(4, endianness); stream.moveForwardsBy(format === 2 ? 20 : 12); return readString(stream, strtabOffset, nameOffset); } input = new Uint8Array(input); const stream = new Stream(input); const result = ["=".repeat(align) + " ELF Header " + "=".repeat(align)]; result.push(elfHeader(stream) + "\n"); getNamesOffset(stream); result.push("=".repeat(align) + " Program Header " + "=".repeat(align)); stream.moveTo(phoff); for (let i = 0; i < phEntries; i++) result.push(programHeader(stream) + "\n"); result.push("=".repeat(align) + " Section Header " + "=".repeat(align)); stream.moveTo(shoff); for (let i = 0; i < shEntries; i++) result.push(sectionHeader(stream) + "\n"); result.push("=".repeat(align) + " Symbol Table " + "=".repeat(align)); stream.moveTo(symtabOffset); let elem = ""; for (let i = 0; i < (symtabSize / symtabEntSize); i++) if ((elem = getSymbols(stream)) !== "") result.push("Symbol Name:".padEnd(align) + elem); return result.join("\n"); } } export default ELFInfo;