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)] #[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, GnuHash = 0x6FFFFFF6, } 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::GnuHash => "GNU_HASH".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 { // if *self == ElfSectionFlags::WRITE { " A".fmt(formatter); // } Ok(()) } }