---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