Chapter 03無料公開

ELF Header

Drumato
Drumato
2021.08.29に更新
このチャプターの目次

ELF header

ELF headerとは,
ELF fileの先頭にある,64bytes(32bit版なら52bytes)のfieldです.

file全体の情報や,{section, segment} header tableのoffsetが格納されているので,
ELFを扱うsoftwareは例外なくこのheaderのparse から始まります.
ということで,ELF header parserを作ってみましょう.

binary parserを作るときは,それに対応する構造体表現を作る手法が一般的です.
C言語でELF parserを作るときは elf.h を使うことでその構造体を利用できますが,
今回は構造体を手書きすることで,ELFの構造を理解するapproachを取ってみます.

よく見るメンバ

ここでは64bit ELF Headerのうちよく参照することになるfieldを紹介します.
すべてのメンバの紹介はしません.
こちらの資料が参考になります.

elf(5) — Linux manual page
ELF入門 - 入門ログ

name size(bytes) description
magic number 4 0x7f, 0x45, 0x4c, 0x46
elf class 1 32bit/64bit object file
elf data 1 lsb/msb
elf osabi 1 build target(OS/ABI)
e_type 2 reloc objやcore dump等,file kind
e_machine 2 ArmやAMD64などtarget architecture
e_entry 8 entry point address. 大抵startup routine symbol address
e_phoff 8 program header tableのstart offset
e_shoff 8 section header tableのstart offset
e_phnum 2 program header tableのentry数
e_shnum 2 section header tableのentry数

elf dataについては Data Encoding - Linker and Libraries Guide を読むと良いです.

例えばsection header tableをparseしたかったら,
e_shoffにseekして,そこからsizeof(Elf64_Shdr) * e_shnum 分をそれぞれdeserializeすれば良い,ということになります.

parser

今回はparser combinator crateのnomを使ってsimpleに実装します.
nomについての解説は行わないので,気になる方はご自身で調べてみてください.

binary parserを作るとき,
細かいfieldのparserをまず書いていって,
それらを組み合わせていく "building-block method" がおすすめです.
ということで,先頭から一つずつparser sampleをお見せします.
大体作り方は同じなので,一部のみ掲載します.
https://github.com/Drumato/elf-parser-in-rust-book/tree/impl にすべて載ってあります.

test helper

その前に,適当にparser test helperを作っておきます.
本来はconsumed inputの比較もすると良いと思います.

// src/parser/header.rs
#[cfg(test)]
mod tests {
    use super::*;

    fn helper<'a, T>(
        parser: impl Fn(&'a [u8]) -> IResult<&'a [u8], T>,
        input: &'a [u8],
        expected: T,
    ) where
        T: std::fmt::Debug + PartialEq + PartialOrd,
    {
        let parse_result = parser(input);
        assert!(parse_result.is_ok());

        assert_eq!(parse_result.unwrap().1, expected);
    }

    fn helper_fail<'a, T>(parser: impl Fn(&'a [u8]) -> IResult<&'a [u8], T>, input: &'a [u8])
    where
        T: std::fmt::Debug + PartialEq + PartialOrd,
    {
        let parse_result = parser(input);
        assert!(parse_result.is_err());
    }
}

magic number

// src/elf/header.rs
pub static ELF_MAGIC_SIGNATURE: &[u8] = b"\x7fELF";

// src/parser/header.rs
fn parse_elf_magic_number(raw: &[u8]) -> IResult<&[u8], &[u8]> {
    tag(ELF_MAGIC_SIGNATURE)(raw)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn elf_magic_number_test() {
        helper(
            parse_elf_magic_number,
            ELF_MAGIC_SIGNATURE,
            ELF_MAGIC_SIGNATURE,
        );
        helper_fail(parse_elf_magic_number, b"\x7fFLF");
    }
}

class

// src/elf/header.rs
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
#[repr(u8)]
pub enum ElfClass {
    None = 0,
    Bit32 = 1,
    Bit64 = 2,
    Num = 3,
    Unknown,
}

impl From<u8> for ElfClass {
    fn from(b: u8) -> ElfClass {
        match b {
            0 => ElfClass::None,
            1 => ElfClass::Bit32,
            2 => ElfClass::Bit64,
            3 => ElfClass::Num,
            _ => ElfClass::Unknown,
        }
    }
}

// src/parser/header.rs
fn parse_elf_class(raw: &[u8]) -> IResult<&[u8], ElfClass> {
    map(read_u8, |byte: u8| ElfClass::from(byte))(raw)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn elf_class_test() {
        helper(parse_elf_class, &[0x00], ElfClass::None);
        helper(parse_elf_class, &[0x01], ElfClass::Bit32);
        helper(parse_elf_class, &[0x02], ElfClass::Bit64);
        helper(parse_elf_class, &[0x03], ElfClass::Num);
        helper(parse_elf_class, &[0xff], ElfClass::Unknown);
    }
}

data

// src/elf/header.rs
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
#[repr(u8)]
pub enum ElfData {
    None = 0,
    Lsb = 1,
    Msb = 2,
    Num = 3,
    Unknown,
}

impl From<u8> for ElfData {
    fn from(b: u8) -> ElfData {
        match b {
            0 => ElfData::None,
            1 => ElfData::Lsb,
            2 => ElfData::Msb,
            3 => ElfData::Num,
            _ => ElfData::Unknown,
        }
    }
}

// src/parser/header.rs
fn parse_elf_data(raw: &[u8]) -> IResult<&[u8], ElfData> {
    map(read_u8, |byte: u8| ElfData::from(byte))(raw)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn elf_data_test() {
        helper(parse_elf_data, &[0x00], ElfData::None);
        helper(parse_elf_data, &[0x01], ElfData::Lsb);
        helper(parse_elf_data, &[0x02], ElfData::Msb);
        helper(parse_elf_data, &[0x03], ElfData::Num);
        helper(parse_elf_data, &[0xff], ElfData::Unknown);
    }
}

identification

上記sub parserを組み合わせてelf identificationのparserを組み立てる事ができます.
nom::multi::tuple() 等を使うといちいち呼び出さなくても良くなるんですが,
fieldの順番を覚えていないうちは律儀に一個一個書いていけば良いと思います.

// src/elf/header.rs
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct ElfIdentification {
    pub class: ElfClass,
    pub data: ElfData,
    pub version: ElfVersion,
    pub osabi: ElfOsAbi,
    pub abi_version: u8,
}

// src/parser/header.rs
fn parse_elf_identification(raw: &[u8]) -> IResult<&[u8], ElfIdentification> {
    let (r, _) = parse_elf_magic_number(raw)?;
    let (r, class) = parse_elf_class(r)?;
    let (r, data) = parse_elf_data(r)?;
    let (r, version) = parse_elf_version(r)?;
    let (r, osabi) = parse_elf_osabi(r)?;
    let (r, abi_version) = parse_elf_abi_version(r)?;
    let (r, _padding) = count(read_u8, 7)(r)?;

    Ok((
        r,
        ElfIdentification {
            class,
            data,
            version,
            osabi,
            abi_version,
        },
    ))
}


#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn elf_identification_test() {
        helper(
            parse_elf_identification,
            &[
                0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            ],
            ElfIdentification {
                class: ElfClass::Bit64,
                data: ElfData::Lsb,
                version: ElfVersion::Current,
                osabi: ElfOsAbi::SysV,
                abi_version: 0x00,
            },
        );
    }
}

64bit elf header

最後にこれらを組み合わせてelf headerを作ります.
本当は elf typeやe_machineもちゃんとenumにするべき なんですが,完全に力尽きました...
(無料なので許してください)

// src/elf/header.rs
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct ElfHeader64 {
    pub id: ElfIdentification,
    pub ty: u16,
    pub machine: u16,
    pub version: u32,
    pub entry: u64,
    pub pht_offset: u64,
    pub sht_offset: u64,
    pub flags: u32,
    pub size: u16,
    pub pht_entry_size: u16,
    pub pht_entries: u16,
    pub sht_entry_size: u16,
    pub sht_entries: u16,
    pub sht_string_table_index: u16,
}

// src/parser/header.rs
pub fn parse_64bit_elf_header(raw: &[u8]) -> IResult<&[u8], ElfHeader64> {
    let (r, id) = parse_elf_identification(raw)?;
    let (r, ty) = le_u16(r)?;
    let (r, machine) = le_u16(r)?;
    let (r, version) = le_u32(r)?;
    let (r, entry) = le_u64(r)?;
    let (r, pht_offset) = le_u64(r)?;
    let (r, sht_offset) = le_u64(r)?;
    let (r, flags) = le_u32(r)?;
    let (r, size) = le_u16(r)?;
    let (r, pht_entry_size) = le_u16(r)?;
    let (r, pht_entries) = le_u16(r)?;
    let (r, sht_entry_size) = le_u16(r)?;
    let (r, sht_entries) = le_u16(r)?;
    let (r, sht_string_table_index) = le_u16(r)?;
    Ok((
        r,
        ElfHeader64 {
            id,
            ty,
            machine,
            version,
            entry,
            pht_offset,
            sht_offset,
            flags,
            size,
            pht_entry_size,
            pht_entries,
            sht_entry_size,
            sht_entries,
            sht_string_table_index,
        },
    ))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn elf_header64_test() {
        helper(
            parse_64bit_elf_header_64bit,
            &[
                // 0x00 ~ 0x10
                0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
                // 0x10 ~ 0x20
                0x03, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00, 
                0x60, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
                // 0x20 ~ 0x30
                0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
                0x88, 0x1c, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 
                // 0x30 ~ 0x40
                0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 
                0x0c, 0x00, 0x40, 0x00, 0x2b, 0x00, 0x2a, 0x00,
            ],
            ElfHeader64 {
                id: ElfIdentification {
                    class: ElfClass::Bit64,
                    data: ElfData::Lsb,
                    version: ElfVersion::Current,
                    osabi: ElfOsAbi::SysV,
                    abi_version: 0x00,
                },
                ty: 0x03,
                machine: 0x3e,
                version: 0x01,
                entry: 0x9560,
                pht_offset: 64,
                sht_offset: 4332680,
                flags: 0x0,
                size: 64,
                pht_entry_size: 56,
                pht_entries: 12,
                sht_entry_size: 64,
                sht_entries: 43,
                sht_string_table_index: 42,
            },
        );
    }
}

appendix

32bit版のheader parsingも同様に実装することができます.
興味のある人は取り組んでみてください.
また,elf classを読む事でparse時に32bit/64bit parserを切り替える事ができます.
これによって自作readelfを32bit/64bitに対応させる事ができます.
拙作のelf-utilitiesでも同じようにしています.