#!/usr/bin/env ruby

# $Id: elfbase.rb 9937 2010-07-27 18:03:18Z jduck $

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
