use bitflags::bitflags; #[repr(C, packed)] #[derive(Debug, Copy, Clone)] pub struct ElfIdent { /// Magic number - 0x7F, then 'ELF' in ASCII pub magic: [u8; 4], /// 1 = 32 bit, 2 = 64 bit pub class: Class, // 1 = little endian, 2 = big endian pub data: Endian, /// ELF header version pub version: u8, /// OS ABI - usually 0 for System V pub OSABI: OSABI, // This field is used to distinguish among incompatible versions of an ABI. pub ABIVersion: u8, /// Unused/padding pub _unused: [u8; 7], } impl std::fmt::Display for ElfIdent { fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str(" Magic: ")?; for b in unsafe { *(std::ptr::addr_of!(self.magic) as *const [u8; 16]) } { formatter.pad(&format!("{:02x?} ", b))?; } formatter.write_str("\n")?; formatter.write_fmt(format_args!( " Class: {:?}\n", self.class ))?; formatter.write_fmt(format_args!( " Data: {}\n", self.data ))?; formatter.write_fmt(format_args!( " Version: {} ({})\n", self.version, match self.version { 0 => "invalid", 1 => "current", 2_u8..=u8::MAX => todo!(), // need to cover all cases } ))?; formatter.write_fmt(format_args!( " OS/ABI: {}\n", self.OSABI ))?; formatter.write_fmt(format_args!( " ABI Version: {}\n", self.ABIVersion )) } } // https://man7.org/linux/man-pages/man5/elf.5.html // https://wiki.osdev.org/ELF // https://en.wikipedia.org/wiki/Executable_and_Linkable_Format #[repr(C)] #[derive(Debug)] pub struct ElfHeader { pub ident: ElfIdent, /// 1 = relocatable, 2 = executable, 3 = shared, 4 = core pub r#type: Type, /// Instruction set - see table below pub machine: Machine, /// ELF Version pub version: u32, /// Program entry position (virtual) pub entry: u64, /// Program header table position (bytes into file) pub phoff: u64, /// Section header table position (bytes into file) pub shoff: u64, /// This member holds processor-specific flags associated with the file /// Currently, no flags have been defined. pub flags: u32, /// Header size pub ehsize: u16, /// Size of an entry in the program header table pub phentsize: u16, /// Number of entries in the program header table pub phnum: u16, /// Size of an entry in the section header table pub shentsize: u16, /// Number of entries in the section header table pub shnum: u16, /// Index in section header table with the section names pub shstrndx: u16, } impl std::fmt::Display for ElfHeader { fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { // yes, this is really what they do in readelf.c formatter.write_fmt(format_args!("{}", self.ident))?; formatter.write_fmt(format_args!( " Type: {}\n", self.r#type ))?; formatter.write_fmt(format_args!( " Machine: {}\n", self.machine ))?; formatter.write_fmt(format_args!( " Version: {:#02x?}\n", self.version ))?; formatter.write_fmt(format_args!( " Entry point address: {:#x?}\n", self.entry ))?; formatter.write_fmt(format_args!( " Start of program headers: {} (bytes into file)\n", self.phoff ))?; formatter.write_fmt(format_args!( " Start of section headers: {} (bytes into file)\n", self.shoff ))?; formatter.write_fmt(format_args!( " Flags: {:#x?}\n", self.flags ))?; formatter.write_fmt(format_args!( " Size of this header: {} (bytes)\n", self.ehsize ))?; formatter.write_fmt(format_args!( " Size of program headers: {} (bytes)\n", self.phentsize ))?; formatter.write_fmt(format_args!( " Number of program headers: {}\n", self.phnum ))?; formatter.write_fmt(format_args!( " Size of section headers: {} (bytes)\n", self.shentsize ))?; formatter.write_fmt(format_args!( " Number of section headers: {}\n", self.shnum ))?; formatter.write_fmt(format_args!( " Section header string table index: {}\n", self.shstrndx )) } } #[derive(Debug, Copy, Clone)] #[repr(u16)] pub enum Type { None = 0, Rel = 1, Exec = 2, Dyn = 3, Core = 4, } impl std::fmt::Display for Type { fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Type::None => formatter.write_str("NONE (None)"), Type::Rel => formatter.write_str("REL (Relocatable file)"), Type::Exec => formatter.write_str("EXEC (Executable file)"), Type::Dyn => formatter.write_str("DYN (Shared object file)"), Type::Core => formatter.write_str("CORE (Core file)"), } } } #[derive(Debug, Copy, Clone)] #[repr(u16)] pub enum Machine { // there are many many many more, im just gonna do x86 // https://github.com/bminor/binutils-gdb/blob/master/binutils/readelf.c#L2746 None = 1, X86_64 = 62, } impl std::fmt::Display for Machine { fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Machine::None => formatter.write_str("None"), Machine::X86_64 => formatter.write_str("Advanced Micro Devices X86-64"), } } } #[derive(Debug, Copy, Clone)] #[repr(u8)] pub enum Class { ELF32 = 1, ELF64 = 2, } #[derive(Debug, Copy, Clone)] #[repr(u8)] pub enum OSABI { None = 0x00, SystemV = 0x01, // HPUX = 0x02, // Solaris = 0x03, // IRIX = 0x04, // FreeBSD = 0x05, // TRU64 = 0x06, // ARM = 0x07, Standalone = 0x08, } impl std::fmt::Display for OSABI { fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { match self { OSABI::None => formatter.write_str("UNIX - System V"), OSABI::SystemV => formatter.write_str("UNIX - System V"), OSABI::Standalone => formatter.write_str("Standalone"), } } } #[derive(Debug, Copy, Clone)] #[repr(u8)] pub enum Endian { Little = 1, Big = 2, } impl std::fmt::Display for Endian { fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Endian::Little => formatter.write_str("2's complement, little endian"), Endian::Big => formatter.write_str("1's complement, big endian"), } } } #[derive(Debug, Copy, Clone)] #[repr(C)] pub struct ElfSectionHeader { /// This member specifies the name of the section. Its value /// is an index into the section header string table section, /// giving the location of a null-terminated string. pub name: u32, /// This member categorizes the section's contents and /// semantics. pub r#type: ElfSectionType, /// Sections support one-bit flags that describe miscellaneous /// attributes. If a flag bit is set in sh_flags, the /// attribute is "on" for the section. Otherwise, the /// attribute is "off" or does not apply. Undefined /// attributes are set to zero. pub flags: ElfSectionFlags, /// If this section appears in the memory image of a process, /// this member holds the address at which the section's first /// byte should reside. Otherwise, the member contains zero. pub addr: u64, /// This member's value holds the byte offset from the /// beginning of the file to the first byte in the section. /// One section type, SHT_NOBITS, occupies no space in the /// file, and its sh_offset member locates the conceptual /// placement in the file. pub offset: u64, /// This member holds the section's size in bytes. Unless the /// section type is SHT_NOBITS, the section occupies sh_size /// bytes in the file. A section of type SHT_NOBITS may have /// a nonzero size, but it occupies no space in the file. pub size: u64, /// This member holds a section header table index link, whose /// interpretation depends on the section type. pub link: u32, /// This member holds extra information, whose interpretation /// depends on the section type. pub info: u32, /// Some sections have address alignment constraints. If a /// section holds a doubleword, the system must ensure /// doubleword alignment for the entire section. That is, the /// value of sh_addr must be congruent to zero, modulo the /// value of sh_addralign. Only zero and positive integral /// powers of two are allowed. The value 0 or 1 means that /// the section has no alignment constraints. pub addralign: u64, /// Some sections hold a table of fixed-sized entries, such as /// a symbol table. For such a section, this member gives the /// size in bytes for each entry. This member contains zero /// if the section does not hold a table of fixed-size /// entries. pub entsize: u64, } impl std::fmt::Display for ElfSectionHeader { fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_fmt(format_args!( "{: <16} {:0>16x} {:0>8x}\n {:0>16x} {:0>16x} {: <10} {: <5} {: <5} {}", self.r#type, self.addr, self.offset, self.size, self.entsize, self.flags, self.link, self.info, self.addralign ))?; Ok(()) } } #[derive(Debug, Copy, Clone, PartialEq)] #[repr(u32)] pub enum ElfSectionType { /// This value marks the section header as inactive. /// It does not have an associated section. Other /// members of the section header have undefined /// values. Null = 0, /// This section holds information defined by the /// program, whose format and meaning are determined /// solely by the program. ProgBits = 1, /// This section holds a symbol table. Typically, /// SHT_SYMTAB provides symbols for link editing, /// though it may also be used for dynamic linking. As /// a complete symbol table, it may contain many /// symbols unnecessary for dynamic linking. An object /// file can also contain a SHT_DYNSYM section. SymTab = 2, /// This section holds a string table. An object file /// may have multiple string table sections. StrTab = 3, /// This section holds relocation entries with explicit /// addends, such as type Elf32_Rela for the 32-bit /// class of object files. An object may have multiple /// relocation sections. Rela = 4, /// This section holds a symbol hash table. An object /// participating in dynamic linking must contain a /// symbol hash table. An object file may have only /// one hash table. Hash = 5, /// This section holds information for dynamic linking. /// An object file may have only one dynamic section. Dynamic = 6, /// This section holds notes (ElfN_Nhdr). Note = 7, /// A section of this type occupies no space in the /// file but otherwise resembles SHT_PROGBITS. /// Although this section contains no bytes, the /// sh_offset member contains the conceptual file /// offset. NoBits = 8, /// This section holds relocation offsets without /// explicit addends, such as type Elf32_Rel for the /// 32-bit class of object files. An object file may /// have multiple relocation sections. Rel = 9, /// This section is reserved but has unspecified /// semantics. ShLib = 10, /// This section holds a minimal set of dynamic linking /// symbols. An object file can also contain a /// SHT_SYMTAB section. DynSym = 11, InitArray = 14, FiniArray = 15, GnuHash = 0x6FFFFFF6, VerNeed = 0x6FFFFFFE, VerSym = 0x6FFFFFFF, } impl std::fmt::Display for ElfSectionType { fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { match self { ElfSectionType::Null => "NULL".fmt(formatter), ElfSectionType::ProgBits => "PROGBITS".fmt(formatter), ElfSectionType::SymTab => "SYMTAB".fmt(formatter), ElfSectionType::StrTab => "STRTAB".fmt(formatter), ElfSectionType::Rela => "RELA".fmt(formatter), ElfSectionType::Hash => "HASH".fmt(formatter), ElfSectionType::Dynamic => "DYNAMIC".fmt(formatter), ElfSectionType::Note => "NOTE".fmt(formatter), ElfSectionType::NoBits => "NOBITS".fmt(formatter), ElfSectionType::Rel => "REL".fmt(formatter), ElfSectionType::ShLib => "SHLIB".fmt(formatter), ElfSectionType::DynSym => "DYNSYM".fmt(formatter), ElfSectionType::InitArray => "INIT_ARRAY".fmt(formatter), ElfSectionType::FiniArray => "FINI_ARRAY".fmt(formatter), ElfSectionType::GnuHash => "GNU_HASH".fmt(formatter), ElfSectionType::VerNeed => "VERNEED".fmt(formatter), ElfSectionType::VerSym => "VERSYM".fmt(formatter), } } } bitflags! { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ElfSectionFlags: u64 { const WRITE = 0x1; const ALLOC = 0x2; const EXEC = 0x4; } } // PAIN IN THE ASS!!!!!!! // https://github.com/aixoss/binutils/blob/master/binutils/readelf.c#L4966 // https://github.com/llvm-mirror/llvm/blob/2c4ca6832fa6b306ee6a7010bfb80a3f2596f824/tools/llvm-readobj/ELFDumper.cpp#L1176 impl std::fmt::Display for ElfSectionFlags { fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { format!( "{}{}{}", { if self.contains(ElfSectionFlags::WRITE) { "W" } else { "" } }, { if self.contains(ElfSectionFlags::ALLOC) { "A" } else { "" } }, { if self.contains(ElfSectionFlags::EXEC) { "X" } else { "" } } ) .fmt(formatter); Ok(()) } } #[derive(Debug, Clone, PartialEq, Eq)] #[repr(C)] pub struct ElfSymbol { /// This member holds an index into the symbol table's string table, /// which holds the character representations of the symbol names. If the /// value is non-zero, it represents a string table index that gives the /// symbol name. Otherwise, the symbol table entry has no name. pub name: u32, /// This member specifies the symbol's type and binding attributes. pub info: u8, /// This member currently specifies a symbol's visibility. pub other: u8, /// Every symbol table entry is defined in relation to some section. This /// member holds the relevant section header table index. As the sh_link and /// sh_info interpretation table and the related text describe, some section /// indexes indicate special meanings. /// /// If this member contains SHN_XINDEX, then the actual section header index /// is too large to fit in this field. The actual value is contained in the /// associated section of type SHT_SYMTAB_SHNDX. pub shndx: u16, /// This member gives the value of the associated symbol. Depending on the /// context, this may be an absolute value, an address, and so on. /// /// * In relocatable files, st_value holds alignment constraints for a /// symbol whose section index is SHN_COMMON. /// * In relocatable files, st_value holds a section offset for a defined /// symbol. st_value is an offset from the beginning of the section that /// st_shndx identifies. /// * In executable and shared object files, st_value holds a virtual /// address. To make these files' symbols more useful for the dynamic /// linker, the section offset (file interpretation) gives way to a /// virtual address (memory interpretation) for which the section number /// is irrelevant. pub value: u64, /// This member gives the symbol's size. /// For example, a data object's size is the number of bytes contained in /// the object. This member holds 0 if the symbol has no size or an unknown /// size. pub size: u64, } #[derive(Debug)] #[repr(C)] pub struct ElfProgramHeader { pub r#type: ElfProgramType, pub flags: ElfProgramFlags, pub offset: u64, pub vaddr: u64, pub paddr: u64, pub filesz: u64, pub memsz: u64, pub align: u64, } impl std::fmt::Display for ElfProgramHeader { fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_fmt(format_args!( "{: <14} {:#016x} {:#016x} {:#016x}\n {:#016x} {:#016x} {: <10} {:#08x}", self.r#type, self.offset, self.vaddr, self.paddr, self.filesz, self.memsz, self.flags, self.align, ))?; Ok(()) } } #[derive(PartialEq,Debug)] #[repr(u32)] pub enum ElfProgramType { /// This value marks the section header as inactive. /// It does not have an associated section. Other /// members of the section header have undefined /// values. PHDR = 6, Interp = 3, Load = 1, Dynamic = 2, Note = 4, GnuProperty = 0x6474E553, GnuEhFrame = 0x6474E550, GnuStack = 0x6474E551, GnuRelro = 0x6474E552, } impl std::fmt::Display for ElfProgramType { fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { match self { ElfProgramType::PHDR => "PHDR".fmt(formatter), ElfProgramType::Interp => "INTERP".fmt(formatter), ElfProgramType::Note => "NOTE".fmt(formatter), ElfProgramType::Load => "LOAD".fmt(formatter), ElfProgramType::Dynamic => "DYNAMIC".fmt(formatter), ElfProgramType::GnuProperty => "GNU_PROPERTY".fmt(formatter), ElfProgramType::GnuEhFrame => "GNU_EH_FRAME".fmt(formatter), ElfProgramType::GnuStack => "GNU_STACK".fmt(formatter), ElfProgramType::GnuRelro => "GNU_RELRO".fmt(formatter), } } } bitflags! { #[derive(Debug)] pub struct ElfProgramFlags: u32 { const EXEC = 0x1; const WRITE = 0x2; const READ = 0x4; } } impl std::fmt::Display for ElfProgramFlags { fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { format!( "{}{}{}", { if self.contains(ElfProgramFlags::READ) { "R" } else { " " } }, { if self.contains(ElfProgramFlags::WRITE) { "W" } else { " " } }, { if self.contains(ElfProgramFlags::EXEC) { "E" } else { " " } } ) .fmt(formatter); Ok(()) } }