#!/usr/bin/env ruby
# -*- coding: binary -*-

require 'rex/struct2'

module Rex
module ElfParsey
class ElfBase

  # ELF Header

  ELF_HEADER_SIZE = 52

  EI_NIDENT = 16

  ELF32_EHDR_LSB = Rex::Struct2::CStructTemplate.new(
    [ 'string',  'e_ident',     EI_NIDENT, '' ],
    [ 'uint16v', 'e_type',      0 ],
    [ 'uint16v', 'e_machine',   0 ],
    [ 'uint32v', 'e_version',   0 ],
    [ 'uint32v', 'e_entry',     0 ],
    [ 'uint32v', 'e_phoff',     0 ],
    [ 'uint32v', 'e_shoff',     0 ],
    [ 'uint32v', 'e_flags',     0 ],
    [ 'uint16v', 'e_ehsize',    0 ],
    [ 'uint16v', 'e_phentsize', 0 ],
    [ 'uint16v', 'e_phnum',     0 ],
    [ 'uint16v', 'e_shentsize', 0 ],
    [ 'uint16v', 'e_shnum',     0 ],
    [ 'uint16v', 'e_shstrndx',  0 ]
  )

  ELF32_EHDR_MSB = Rex::Struct2::CStructTemplate.new(
    [ 'string',  'e_ident',     EI_NIDENT, '' ],
    [ 'uint16n', 'e_type',      0 ],
    [ 'uint16n', 'e_machine',   0 ],
    [ 'uint32n', 'e_version',   0 ],
    [ 'uint32n', 'e_entry',     0 ],
    [ 'uint32n', 'e_phoff',     0 ],
    [ 'uint32n', 'e_shoff',     0 ],
    [ 'uint32n', 'e_flags',     0 ],
    [ 'uint16n', 'e_ehsize',    0 ],
    [ 'uint16n', 'e_phentsize', 0 ],
    [ 'uint16n', 'e_phnum',     0 ],
    [ 'uint16n', 'e_shentsize', 0 ],
    [ 'uint16n', 'e_shnum',     0 ],
    [ 'uint16n', 'e_shstrndx',  0 ]
  )

  # e_type  This member identifies the object file type

  ET_NONE   = 0       # No file type
  ET_REL    = 1       # Relocatable file
  ET_EXEC   = 2       # Executable file
  ET_DYN    = 3       # Shared object file
  ET_CORE   = 4       # Core file
  ET_LOPROC = 0xff00  # Processor-specific
  ET_HIPROC = 0xffff  # Processor-specific

  #
  # e_machine  This member's value specifies the required architecture for an
  # individual file.
  #

  # ET_NONE        = 0   # No machine
  EM_M32         = 1   # AT&T WE 32100
  EM_SPARC       = 2   # SPARC
  EM_386         = 3   # Intel Architecture
  EM_68K         = 4   # Motorola 68000
  EM_88K         = 5   # Motorola 88000
  EM_860         = 7   # Intel 80860
  EM_MIPS        = 8   # MIPS RS3000 Big-Endian
  EM_MIPS_RS4_BE = 10  # MIPS RS4000 Big-Endian

  # e_version  This member identifies the object file version

  EV_NONE    = 0  # Invalid version
  EV_CURRENT = 1  # Current version


  # ELF Identification

  # e_ident[]  Identification indexes

  EI_MAG0    = 0   # File identification
  EI_MAG1    = 1   # File identification
  EI_MAG2    = 2   # File identification
  EI_MAG3    = 3   # File identification
  EI_CLASS   = 4   # File class
  EI_DATA    = 5   # Data encoding
  EI_VERSION = 6   # File version
  EI_PAD     = 7   # Start of padding bytes
  # EI_NIDENT  = 16  # Size of e_ident[]

  #
  # EI_MAG0 to EI_MAG3  A file's first 4 bytes hold a "magic number",
  # identifying the file as an ELF object file.
  #

  ELFMAG0 = 0x7f  # e_ident[EI_MAG0]
  ELFMAG1 = ?E   # e_ident[EI_MAG1]
  ELFMAG2 = ?L   # e_ident[EI_MAG2]
  ELFMAG3 = ?F   # e_ident[EI_MAG3]

  ELFMAG = ELFMAG0.chr + ELFMAG1.chr + ELFMAG2.chr + ELFMAG3.chr

  # EI_CLASS  Identifies the file's class, or capacity

  ELFCLASSNONE = 0  # Invalid class
  ELFCLASS32   = 1  # 32-bit objects
  ELFCLASS64   = 2  # 64-bit objects

  #
  # EI_DATA  Specifies the data encoding of the processor-specific data in
  # the object file. The following encodings are currently defined.
  #

  ELFDATANONE = 0  # Invalid data encoding
  ELFDATA2LSB = 1  # Least significant byte first
  ELFDATA2MSB = 2  # Most significant byte first

  class GenericStruct
    attr_accessor :struct
    def initialize(_struct)
      self.struct = _struct
    end

    # The following methods are just pass-throughs for struct

    # Access a value
    def v
      struct.v

    end

    # Access a value by array
    def [](*args)
      struct[*args]
    end

    # Obtain an array of all fields
    def keys
      struct.keys
    end

    def method_missing(meth, *args)
      v[meth.to_s] || (raise NoMethodError.new, meth)
    end
  end

  class GenericHeader < GenericStruct
  end

  class ElfHeader < GenericHeader
    def initialize(rawdata)

      # Identify the data encoding and parse ELF Header
      elf_header = ELF32_EHDR_LSB.make_struct

      if !elf_header.from_s(rawdata)
        raise ElfHeaderError, "Couldn't parse ELF Header", caller
      end

      if elf_header.v['e_ident'][EI_DATA,1].unpack('C')[0] == ELFDATA2MSB
        elf_header = ELF32_EHDR_MSB.make_struct

        if !elf_header.from_s(rawdata)
          raise ElfHeaderError, "Couldn't parse ELF Header", caller
        end
      end

      unless [ ELFDATA2LSB, ELFDATA2MSB ].include?(
      elf_header.v['e_ident'][EI_DATA,1].unpack('C')[0])
        raise ElfHeaderError, "Invalid data encoding", caller
      end

      # Identify the file as an ELF object file
      unless elf_header.v['e_ident'][EI_MAG0, 4] == ELFMAG
        raise ElfHeaderError, 'Invalid magic number', caller
      end

      self.struct = elf_header
    end

    def e_ident
      struct.v['e_ident']
    end

  end


  # Program Header

  PROGRAM_HEADER_SIZE = 32

  ELF32_PHDR_LSB = Rex::Struct2::CStructTemplate.new(
    [ 'uint32v', 'p_type',   0 ],
    [ 'uint32v', 'p_offset', 0 ],
    [ 'uint32v', 'p_vaddr',  0 ],
    [ 'uint32v', 'p_paddr',  0 ],
    [ 'uint32v', 'p_filesz', 0 ],
    [ 'uint32v', 'p_memsz',  0 ],
    [ 'uint32v', 'p_flags',  0 ],
    [ 'uint32v', 'p_align',  0 ]
  )

  ELF32_PHDR_MSB = Rex::Struct2::CStructTemplate.new(
    [ 'uint32n', 'p_type',   0 ],
    [ 'uint32n', 'p_offset', 0 ],
    [ 'uint32n', 'p_vaddr',  0 ],
    [ 'uint32n', 'p_paddr',  0 ],
    [ 'uint32n', 'p_filesz', 0 ],
    [ 'uint32n', 'p_memsz',  0 ],
    [ 'uint32n', 'p_flags',  0 ],
    [ 'uint32n', 'p_align',  0 ]
  )

  #
  # p_type  This member tells what kind of segment this array element
  # describes or how to interpret the array element's information.
  #

  # Segment Types

  PT_NULL    = 0
  PT_LOAD    = 1
  PT_DYNAMIC = 2
  PT_INTERP  = 3
  PT_NOTE    = 4
  PT_SHLIB   = 5
  PT_PHDR    = 6
  PT_LOPROC  = 0x70000000
  PT_HIPROC  = 0x7fffffff

  class ProgramHeader < GenericHeader
    def initialize(rawdata, ei_data)
      # Identify the data encoding and parse Program Header
      if ei_data == ELFDATA2LSB
        program_header = ELF32_PHDR_LSB.make_struct
      elsif ei_data == ELFDATA2MSB
        program_header = ELF32_PHDR_MSB.make_struct
      else
        raise ElfHeaderError, "Invalid data encoding", caller
      end

      if !program_header.from_s(rawdata)
        raise ProgramHeaderError, "Couldn't parse Program Header", caller
      end

      self.struct = program_header
    end

  end

end
end
end
