---Description
--
-- @args xxx yyyyy
--                   
-- @author Ron Bowes <ron@skullsecurity.net>
-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
-----------------------------------------------------------------------
module(... or "pe", package.seeall)

require 'bit'
require 'bin'
require 'stdnse'
require 'nsedebug'

function parse_from_string(data)
	local pos, _
	local result = {}

	-- Start by reading the first two bytes and ensuring it's a proper executable (they should be 'MZ')
	pos, result['MZ'] = bin.unpack("A2", data)
	if(result['MZ'] == nil or result['MZ'] ~= "MZ") then
		return false, "File does not appear to be a proper executable (doesn't start with 'MZ')"
	end

	-- The 2-byte offset to the actual PE header is located at 0x3C
	pos, result['pe_offset'] = bin.unpack("<S", data, 0x3C + 1)
	if(result['pe_offset'] == nil) then
		return false, "File doesn't not appear to be a proper executable (pe_offset not found)"
	end

	-- Validate we have the proper PE section by checking the 4-byte signature
	pos, result['pe_signature'] = bin.unpack("<I", data, result['pe_offset'] + 1)
	if(result['pe_signature'] == nil or result['pe_signature'] ~= 0x00004550) then -- 'PE\0\0'
		return false, "File doesn't appear to be a PE file (PE signature not found)"
	end

	-- Read the PE header
	pos, result['machine'], result['number_of_sections'], result['time_date_stamp'], result['pointer_to_symbol_table'], result['number_of_symbols'], result['size_of_optional_header'], result['characteristics'] = bin.unpack("<SSIIISS", data, pos)
	if(result['characteristics'] == nil) then
		return false, "File doesn't appear to be a PE file (couldn't read the file header)"
	end

	-- Check if we actually have an optional header
	if(result['size_of_optional'] ~= 0) then
		local optional = {}

		local optional_windows = {}
		_, optional_windows['magic'] = bin.unpack("<S", data, pos)
		if(optional_windows['magic'] == 0x010b) then -- PE32 format
			-- Parse the 'standard' optional headers
			pos, optional_windows['magic'], optional_windows['major_linker_version'], optional_windows['minor_linker_version'], optional_windows['size_of_code'], optional_windows['size_of_uninitialized_data'], optional_windows['address_of_entry_point'], optional_windows['base_of_code'], optional_windows['base_of_data'] = bin.unpack("<SCCIIIIII", data, pos)
			if(optional_windows['base_of_data'] == nil) then
				return false, "File doesn't appear to be a valid PE file (couldn't read the standard optional fields)"
			end

			-- Parse the 'windows-specific' optional headers
			pos, optional_windows['image_base'], optional_windows['section_alignment'], optional_windows['file_alignment'], optional_windows['major_operating_system_version'], optional_windows['minor_operating_system_version'], optional_windows['major_image_version'], optional_windows['minor_image_version'], optional_windows['major_subsystem_version'], optional_windows['minor_subsystem_version'], optional_windows['win32_version_value'], optional_windows['size_of_image'], optional_windows['size_of_headers'], optional_windows['check_sum'], optional_windows['subsystem'], optional_windows['dll_characteristics'], optional_windows['size_of_stack_reserve'], optional_windows['size_of_stack_commit'], optional_windows['size_of_heap_reserve'], optional_windows['size_of_heap_commit'], optional_windows['loader_flags'], optional_windows['number_of_rva_and_sizes'] = bin.unpack("<IIISSSSSSIIIISSIIIIII", data, pos)

		elseif(optional_windows['magic'] == 0x020b) then -- PE32+ format
			-- Parse the 'standard' optional headers
			pos, optional_windows['magic'], optional_windows['major_linker_version'], optional_windows['minor_linker_version'], optional_windows['size_of_code'], optional_windows['size_of_uninitialized_data'], optional_windows['address_of_entry_point'], optional_windows['base_of_code'] = bin.unpack("<SCCIIIII", data, pos)
			if(optional_windows['base_of_code'] == nil) then
				return false, "File doesn't appear to be a valid PE file (couldn't read the standard optional fields)"
			end

			-- Parse the 'windows-specific' optional headers
			pos, optional_windows['image_base'], optional_windows['section_alignment'], optional_windows['file_alignment'], optional_windows['major_operating_system_version'], optional_windows['minor_operating_system_version'], optional_windows['major_image_version'], optional_windows['minor_image_version'], optional_windows['major_subsystem_version'], optional_windows['minor_subsystem_version'], optional_windows['win32_version_value'], optional_windows['size_of_image'], optional_windows['size_of_headers'], optional_windows['check_sum'], optional_windows['subsystem'], optional_windows['dll_characteristics'], optional_windows['size_of_stack_reserve'], optional_windows['size_of_stack_commit'], optional_windows['size_of_heap_reserve'], optional_windows['size_of_heap_commit'], optional_windows['loader_flags'], optional_windows['number_of_rva_and_sizes'] = bin.unpack("<LIISSSSSSIIIISSLLLLII", data, pos)
		else
			return false, string.format("PE had an unrecognized 'magic' value: %x", optional_windows['magic'])
		end

		-- Make sure we haven't run off the string yet
		if(optional_windows['number_of_rva_and_sizes'] == nil) then
			return false, "File doesn't appear to be a valid PE file (couldn't read the windows-specific optional headers)"
		end

		-- Double-check that we have the expected value for NumberOfRvaAndSizes (note: this isn't the best way to do it, but it works for our purpose)
		if(optional_windows['number_of_rva_and_sizes'] ~= 0x10) then
			return false, string.format("NumberOfRvaAndSizes was an unexpected value (values other than 0x10 are okay, but unhandled)")
		end

		-- Save the optional_windows
		optional['optional_windows'] = optional_windows

		-- Parse the data optional_directories
		local optional_directories = {}
		pos, optional_directories['export_address'],                  optional_directories['export_size']                  = bin.unpack("<II", data, pos)
		pos, optional_directories['import_address'],                  optional_directories['import_size']                  = bin.unpack("<II", data, pos)
		pos, optional_directories['resource_address'],                optional_directories['resource_size']                = bin.unpack("<II", data, pos)
		pos, optional_directories['exception_address'],               optional_directories['exception_size']               = bin.unpack("<II", data, pos)
		pos, optional_directories['certificate_address'],             optional_directories['certificate_size']             = bin.unpack("<II", data, pos)
		pos, optional_directories['base_relocation_address'],         optional_directories['base_relocation_size']         = bin.unpack("<II", data, pos)
		pos, optional_directories['debug_address'],                   optional_directories['debug_size']                   = bin.unpack("<II", data, pos)
		pos, optional_directories['architecture_address'],            optional_directories['architecture_size']            = bin.unpack("<II", data, pos)
		pos, optional_directories['global_ptr_address'],              optional_directories['global_ptr_size']              = bin.unpack("<II", data, pos)
		pos, optional_directories['tls_table_address'],               optional_directories['tls_table_size']               = bin.unpack("<II", data, pos)
		pos, optional_directories['load_config_table_address'],       optional_directories['load_config_table_size']       = bin.unpack("<II", data, pos)
		pos, optional_directories['bound_import_address'],            optional_directories['bound_import_size']            = bin.unpack("<II", data, pos)
		pos, optional_directories['iat_address'],                     optional_directories['iat_size']                     = bin.unpack("<II", data, pos)
		pos, optional_directories['delay_import_descriptor_address'], optional_directories['delay_import_descriptor_size'] = bin.unpack("<II", data, pos)
		pos, optional_directories['clr_runtime_header_address'],      optional_directories['clr_runtime_header_size']      = bin.unpack("<II", data, pos)
		pos, optional_directories['reserved_zero_address'],           optional_directories['reserved_zero_size']           = bin.unpack("<II", data, pos)
		optional['optional_directories'] = optional_directories

		-- Verify we had valid results
		if(optional_directories['reserved_zero_size'] == nil) then
			return false, string.format("File doesn't appear to be a valid PE file (couldn't read the directories table)")
		end
		if(optional_directories['reserved_zero_address'] ~= 0 or optional_directories['reserved_zero_size'] ~= 0) then
			return false, string.format("Reserved value in directories system was an unexpected value")
		end

		result['optional'] = optional
	end

	-- Next, parse the section table
	local sections = {}
	for i = 1, result['number_of_sections'], 1 do
		-- Parse the next section
		local section = {}
		pos, section['name'], section['virtual_size'], section['virtual_address'], section['size_of_raw_data'], section['pointer_to_raw_data'], section['pointer_to_relocations'], section['pointer_to_linenumbers'], section['number_of_relocations'], section['number_of_linenumbers'], section['characteristics'] = bin.unpack("<A8IIIIIISSI", data, pos)

		-- Make sure we haven't run out of data
		if(section['characteristics'] == nil) then
			return false, "File doesn't appear to be a valid PE file (couldn't read the section table)"
		end

		-- Fix the name (usually it's null-padded)
		if(string.find(section['name'], string.char(0))) then
			section['name'] = string.sub(section['name'], 1, string.find(section['name'], string.char(0)) - 1)
		end

		sections[section['name']] = section
	end

	result['sections'] = sections


	return true, result










end