class ParsePE32(object): def __init__(self): self.bin_contents = None self.section_headers_info = None self.data_directory_info = None self.pe_section_utils = None self.pe_data_directory_utils = None self.pe_optional_header_utils = None self.image_tls_directory_foff = None def execute(self, bin_path): self.bin_contents = FileUtils.read_file(bin_path) if self.bin_contents: self.parse_pe32() def parse_pe32(self): self.pe_section_utils = PESectionUtils(self.bin_contents) self.section_headers_info = self.pe_section_utils.get_sections_info() self.pe_data_directory_utils = PEDataDirectoryUtils(self.bin_contents) self.data_directory_info = self.pe_data_directory_utils.get_data_directories_info( ) self.pe_optional_header_utils = PEOptionalHeaderUtils( self.bin_contents) self.image_tls_directory_foff = self.get_image_tls_directory_rva() tls_callback_vas = self.get_tls_callbacks() logging.info('TLS Callback addresses({} TLS callbacks):'.format( len(tls_callback_vas))) for callback_va in tls_callback_vas: callback_rva = callback_va - self.pe_optional_header_utils.get_image_base( ) callback_foff = PEGenericUtils.rva_to_file_offset( self.section_headers_info, callback_rva) logging.info(' VA: 0x{:x}, RVA: 0x{:x}, File Offset: {}'.format( callback_va, callback_rva, callback_foff)) def get_image_tls_directory_rva(self): tls_rva = self.data_directory_info[9]['rva'] return tls_rva def get_tls_callbacks(self): tls_callback_vas = [] callback_number = 0 address_of_callbacks_va = -1 while address_of_callbacks_va != 0: address_of_callbacks_va = self.get_address_of_callbacks_va( callback_number) if address_of_callbacks_va: tls_callback_vas.append(address_of_callbacks_va) callback_number += 1 return tls_callback_vas def get_address_of_callbacks_va(self, callback_number): address_of_callbacks_off = 0xC + callback_number * 4 # sizeof(DWORD) image_tls_directory_rva = self.get_image_tls_directory_rva() image_tls_directory_foff = PEGenericUtils.rva_to_file_offset( self.section_headers_info, image_tls_directory_rva) address_of_callbacks_va = PEGenericUtils.unpack_dword( self.bin_contents, image_tls_directory_foff + address_of_callbacks_off) return address_of_callbacks_va
class ParsePE32(object): def __init__(self): self.bin_contents = None self.section_headers_info = None self.data_directory_info = None self.pe_section_utils = None self.pe_data_directory_utils = None self.pe_optional_header_utils = None self.image_tls_directory_foff = None def execute(self, bin_path): self.bin_contents = FileUtils.read_file(bin_path) if self.bin_contents: self.parse_pe32() def parse_pe32(self): self.pe_section_utils = PESectionUtils(self.bin_contents) self.section_headers_info = self.pe_section_utils.get_sections_info() self.pe_data_directory_utils = PEDataDirectoryUtils(self.bin_contents) self.data_directory_info = self.pe_data_directory_utils.get_data_directories_info() self.pe_optional_header_utils = PEOptionalHeaderUtils(self.bin_contents) self.image_tls_directory_foff = self.get_image_tls_directory_rva() tls_callback_vas = self.get_tls_callbacks() logging.info('TLS Callback addresses({} TLS callbacks):'.format(len(tls_callback_vas))) for callback_va in tls_callback_vas: callback_rva = callback_va - self.pe_optional_header_utils.get_image_base() callback_foff = PEGenericUtils.rva_to_file_offset(self.section_headers_info, callback_rva) logging.info(' VA: 0x{:x}, RVA: 0x{:x}, File Offset: {}'.format(callback_va, callback_rva, callback_foff)) def get_image_tls_directory_rva(self): tls_rva = self.data_directory_info[9]['rva'] return tls_rva def get_tls_callbacks(self): tls_callback_vas = [] callback_number = 0 has_more_callbacks = True while has_more_callbacks: address_of_callbacks_va = self.get_address_of_callbacks_va(callback_number) # PE32+: Looks like in PE32+ we still need to check only a DWORD and not a QWORD, so we read a QWORD # as we do to get the callback address, but we clean up the MSBytes. If the LSBytes are 0, end of callbacks has_more_callbacks = address_of_callbacks_va & 0x000000FFFFFFFF != 0x0 if has_more_callbacks: tls_callback_vas.append(address_of_callbacks_va) callback_number += 1 return tls_callback_vas def get_address_of_callbacks_va(self, callback_number): address_of_callbacks_off = 24 + callback_number * 8 # PE32+: fix offset and increase to sizeof(QWORD) image_tls_directory_rva = self.get_image_tls_directory_rva() image_tls_directory_foff = PEGenericUtils.rva_to_file_offset(self.section_headers_info, image_tls_directory_rva) address_of_callbacks_va = PEGenericUtils.unpack_qword(self.bin_contents, image_tls_directory_foff + address_of_callbacks_off) # PE32+: Unpack QWORD return address_of_callbacks_va
class ParsePE32(object): def __init__(self): self.bin_contents = None self.section_headers_info = None self.pe_section_utils = None def execute(self, bin_path): self.bin_contents = FileUtils.read_file(bin_path) if self.bin_contents: self.parse_pe32() def parse_pe32(self): self.pe_section_utils = PESectionUtils(self.bin_contents) self.section_headers_info = self.pe_section_utils.get_sections_info() imported_dlls = self.get_imported_dlls() logging.info('PE Imported DLLs #: {}'.format(len(imported_dlls))) logging.info('PE Imported DLLs: \n- {}'.format('\n- '.join(imported_dlls))) def get_imported_dlls(self): dll_names = [] current_import_descriptor = self.get_first_import_descriptor_foff() import_descriptor_size = 0x14 # self.get_import_descriptor_size() while True: name = self.get_dll_name(current_import_descriptor) if not name: break dll_names.append(name) current_import_descriptor += import_descriptor_size return dll_names def get_data_directory_array_offs(self): # _IMAGE_DATA_DIRECTORY DataDirectory[16]; from OptionalHeader optional_header_offs = self.pe_section_utils.get_optional_header_offs() # PE32+: offsets in Ero's poster are wrong starting with the second QWORD (SizeOfStackReserve). data_directories_offs = optional_header_offs + 0x60 + 0x10 # The +0x10 adjusts it. return data_directories_offs def get_import_directory_offs(self): # IMAGE_DIRECTORY_ENTRY_IMPORT (DataDirectory[1]) data_directory_array_offs = self.get_data_directory_array_offs() image_data_directory_size = 0x8 # two dwords return data_directory_array_offs + image_data_directory_size * 1 def get_import_descriptor_size(self): import_directory_offs = self.get_import_directory_offs() size_offs = 0x4 import_descriptor_size = self._unpack_word(self.bin_contents, import_directory_offs + size_offs) return import_descriptor_size def get_first_import_descriptor_rva(self): import_directory_offs = self.get_import_directory_offs() virtual_address_offs = 0x0 import_descriptor_rva = self._unpack_word(self.bin_contents, import_directory_offs + virtual_address_offs) return import_descriptor_rva def get_first_import_descriptor_foff(self): import_directory_offs = self.get_first_import_descriptor_rva() return self._rva_to_file_offset(import_directory_offs) def get_dll_name(self, import_descriptor_addr): name_offs = 0xC name_rva = self._unpack_dword(self.bin_contents, import_descriptor_addr + name_offs) name_foff = self._rva_to_file_offset(name_rva) name = self._read_null_terminated_ascii_string(name_foff) return name def _read_null_terminated_ascii_string(self, starting_addr): idx = 0 ascii_string = '' while ord(self.bin_contents[starting_addr + idx]) != 0 and starting_addr + idx < len(self.bin_contents): ascii_string += self.bin_contents[starting_addr + idx] idx += 1 return ascii_string def _rva_to_file_offset(self, rva): file_offs = -1 for section_info in self.section_headers_info: if self._rva_belongs_to_section(rva, section_info): file_offs = rva - section_info['Virtual Address'] + section_info['Pointer to Raw Data'] break return file_offs @staticmethod def _rva_belongs_to_section(rva, section_info): return section_info['Virtual Address'] <= rva < section_info['Virtual Address'] + section_info['Virtual Size'] @staticmethod def _unpack_dword(contents, offs): # format string: <: little endian. L: 4 bytes (1 dword) return struct.unpack_from('<L', contents, offs)[0] @staticmethod def _unpack_word(contents, offs): # format string: <: little endian. H: 2 bytes (1 word) return struct.unpack_from('<H', contents, offs)[0]
class ParsePE32(object): def __init__(self): self.bin_contents = None self.section_headers_info = None self.data_directory_info = None self.pe_section_utils = None self.pe_data_directory_utils = None def execute(self, bin_path): self.bin_contents = FileUtils.read_file(bin_path) if self.bin_contents: self.parse_pe32() def parse_pe32(self): self.pe_section_utils = PESectionUtils(self.bin_contents) self.section_headers_info = self.pe_section_utils.get_sections_info() self.pe_data_directory_utils = PEDataDirectoryUtils(self.bin_contents) self.data_directory_info = self.pe_data_directory_utils.get_data_directories_info( ) imported_dlls_functions = self.get_pe_dll_imported_functions() logging.info('PE Imported DLLs #: {}'.format( len(imported_dlls_functions))) for imported_dll_functions in imported_dlls_functions: logging.info('- {}: {}'.format( imported_dll_functions['dll_name'], ', '.join(imported_dll_functions['imported_functions']))) def get_pe_dll_imported_functions(self): imported_dll_functions = [] current_import_descriptor = self.get_first_import_descriptor_foff() import_descriptor_size = 0x14 while True: dll_name = self.get_dll_name(current_import_descriptor) if dll_name: imported_functions = self.get_dll_imported_functions( current_import_descriptor) imported_dll_functions.append({ 'dll_name': dll_name, 'imported_functions': imported_functions }) current_import_descriptor += import_descriptor_size else: break return imported_dll_functions def get_first_import_descriptor_foff(self): import_directory_rva = self.data_directory_info[1]['rva'] return self._rva_to_file_offset(import_directory_rva) def get_dll_name(self, import_descriptor_addr): name_offs = 0xC name_rva = self._unpack_dword(self.bin_contents, import_descriptor_addr + name_offs) name_foff = self._rva_to_file_offset(name_rva) name = self._read_null_terminated_ascii_string(name_foff) return name def get_dll_imported_functions(self, import_descriptor_addr): function_names = [] original_first_thunk_foff = self.get_original_first_thunk_foff( import_descriptor_addr) while True: function_name = self.get_imported_function_name( original_first_thunk_foff) if function_name: function_names.append(function_name) # PE32+: FirstThunk and OriginalFirstThunk point to QWORD in PE32+ instead of DWORD original_first_thunk_foff += 0x8 else: break return function_names def get_original_first_thunk_foff(self, import_descriptor_addr): original_first_thunk_off = 0x0 original_first_thunk_rva = self._unpack_dword( self.bin_contents, import_descriptor_addr + original_first_thunk_off) original_first_thunk_foff = self._rva_to_file_offset( original_first_thunk_rva) return original_first_thunk_foff def get_imported_function_name(self, first_thunk_foff): name = None name_off = 0x2 image_import_by_name_foff = self.get_import_by_name_foff( first_thunk_foff) if image_import_by_name_foff != -1: name = self._read_null_terminated_ascii_string( image_import_by_name_foff + name_off) return name def get_import_by_name_foff(self, first_thunk_foff): address_of_data_off = 0 # PE32+: FirstThunk and OriginalFirstThunk point to QWORD in PE32+ instead of DWORD import_by_name_rva = self._unpack_qword( self.bin_contents, first_thunk_foff + address_of_data_off) import_by_name_foff = self._rva_to_file_offset(import_by_name_rva) return import_by_name_foff def _read_null_terminated_ascii_string(self, starting_addr): idx = 0 ascii_string = '' while ord(self.bin_contents[starting_addr + idx]) != 0 and starting_addr + idx < len( self.bin_contents): ascii_string += self.bin_contents[starting_addr + idx] idx += 1 return ascii_string def _rva_to_file_offset(self, rva): file_offs = -1 for section_info in self.section_headers_info: if self._rva_belongs_to_section(rva, section_info): file_offs = rva - section_info[ 'Virtual Address'] + section_info['Pointer to Raw Data'] break return file_offs @staticmethod def _rva_belongs_to_section(rva, section_info): return section_info['Virtual Address'] <= rva < section_info[ 'Virtual Address'] + section_info['Virtual Size'] @staticmethod def _unpack_qword(contents, offs): # format string: <: little endian. Q: 8 bytes (2 dword) return struct.unpack_from('<Q', contents, offs)[0] @staticmethod def _unpack_dword(contents, offs): # format string: <: little endian. L: 4 bytes (1 dword) return struct.unpack_from('<L', contents, offs)[0]
class ParsePE32(object): def __init__(self): self.bin_contents = None self.section_headers_info = None self.pe_section_utils = None def execute(self, bin_path): self.bin_contents = FileUtils.read_file(bin_path) if self.bin_contents: self.parse_pe32() def parse_pe32(self): self.pe_section_utils = PESectionUtils(self.bin_contents) self.section_headers_info = self.pe_section_utils.get_sections_info() imported_dlls_functions = self.get_pe_dll_imported_functions() logging.info('PE Imported DLLs #: {}'.format( len(imported_dlls_functions))) for imported_dll_functions in imported_dlls_functions: logging.info('- {}: {}'.format( imported_dll_functions['dll_name'], ', '.join(imported_dll_functions['imported_functions']))) def get_pe_dll_imported_functions(self): imported_dll_functions = [] current_import_descriptor = self.get_first_import_descriptor_foff() import_descriptor_size = 0x14 while True: dll_name = self.get_dll_name(current_import_descriptor) if dll_name: imported_functions = self.get_dll_imported_functions( current_import_descriptor) imported_dll_functions.append({ 'dll_name': dll_name, 'imported_functions': imported_functions }) current_import_descriptor += import_descriptor_size else: break return imported_dll_functions def get_first_import_descriptor_foff(self): import_directory_offs = self.get_first_import_descriptor_rva() return self._rva_to_file_offset(import_directory_offs) def get_first_import_descriptor_rva(self): import_directory_offs = self.get_import_directory_offs() virtual_address_offs = 0x0 import_descriptor_rva = self._unpack_word( self.bin_contents, import_directory_offs + virtual_address_offs) return import_descriptor_rva def get_import_directory_offs( self): # IMAGE_DIRECTORY_ENTRY_IMPORT (DataDirectory[1]) data_directory_array_offs = self.get_data_directory_array_offs() image_data_directory_size = 0x8 # two dwords return data_directory_array_offs + image_data_directory_size * 1 def get_data_directory_array_offs( self ): # _IMAGE_DATA_DIRECTORY DataDirectory[16]; from OptionalHeader optional_header_offs = self.pe_section_utils.get_optional_header_offs() # PE32+: offsets in Ero's poster are wrong starting with the second QWORD (SizeOfStackReserve). data_directories_offs = optional_header_offs + 0x60 + 0x10 # The +0x10 adjusts it. return data_directories_offs def get_dll_name(self, import_descriptor_addr): name_offs = 0xC name_rva = self._unpack_dword(self.bin_contents, import_descriptor_addr + name_offs) name_foff = self._rva_to_file_offset(name_rva) name = self._read_null_terminated_ascii_string(name_foff) return name def get_dll_imported_functions(self, import_descriptor_addr): function_names = [] first_thunk_foff = self.get_first_thunk_foff(import_descriptor_addr) while True: function_name = self.get_imported_function_name(first_thunk_foff) if function_name: function_names.append(function_name) # PE32+: FirstThunk and OriginalFirstThunk point to QWORD in PE32+ instead of DWORD first_thunk_foff += 0x8 else: break return function_names def get_first_thunk_foff(self, import_descriptor_addr): first_thunk_off = 0x10 first_thunk_rva = self._unpack_dword( self.bin_contents, import_descriptor_addr + first_thunk_off) first_thunk_foff = self._rva_to_file_offset(first_thunk_rva) return first_thunk_foff def get_imported_function_name(self, first_thunk_foff): name = None name_off = 0x2 image_import_by_name_foff = self.get_import_by_name_foff( first_thunk_foff) if image_import_by_name_foff != -1: name = self._read_null_terminated_ascii_string( image_import_by_name_foff + name_off) return name def get_import_by_name_foff(self, first_thunk_foff): address_of_data_off = 0 # PE32+: FirstThunk and OriginalFirstThunk point to QWORD in PE32+ instead of DWORD import_by_name_rva = self._unpack_qword( self.bin_contents, first_thunk_foff + address_of_data_off) import_by_name_foff = self._rva_to_file_offset(import_by_name_rva) return import_by_name_foff def _read_null_terminated_ascii_string(self, starting_addr): idx = 0 ascii_string = '' while ord(self.bin_contents[starting_addr + idx]) != 0 and starting_addr + idx < len( self.bin_contents): ascii_string += self.bin_contents[starting_addr + idx] idx += 1 return ascii_string def _rva_to_file_offset(self, rva): file_offs = -1 for section_info in self.section_headers_info: if self._rva_belongs_to_section(rva, section_info): file_offs = rva - section_info[ 'Virtual Address'] + section_info['Pointer to Raw Data'] break return file_offs @staticmethod def _rva_belongs_to_section(rva, section_info): return section_info['Virtual Address'] <= rva < section_info[ 'Virtual Address'] + section_info['Virtual Size'] @staticmethod def _unpack_qword(contents, offs): # format string: <: little endian. Q: 8 bytes (2 dword) return struct.unpack_from('<Q', contents, offs)[0] @staticmethod def _unpack_dword(contents, offs): # format string: <: little endian. L: 4 bytes (1 dword) return struct.unpack_from('<L', contents, offs)[0] @staticmethod def _unpack_word(contents, offs): # format string: <: little endian. H: 2 bytes (1 word) return struct.unpack_from('<H', contents, offs)[0]
class ParsePE32(object): def __init__(self): self.bin_contents = None self.section_headers_info = None self.data_directory_info = None self.pe_section_utils = None self.pe_data_directory_utils = None self.export_directory_foff = None def execute(self, bin_path): self.bin_contents = FileUtils.read_file(bin_path) if self.bin_contents: self.parse_pe32() def parse_pe32(self): """ Same as x86, the only thing that changes is pe_data_directory_utils! """ self.pe_section_utils = PESectionUtils(self.bin_contents) self.section_headers_info = self.pe_section_utils.get_sections_info() self.pe_data_directory_utils = PEDataDirectoryUtils(self.bin_contents) self.data_directory_info = self.pe_data_directory_utils.get_data_directories_info( ) self.export_directory_foff = self.get_export_descriptor_directory_foff( ) if self.has_exports(): number_of_functions = self.get_number_of_functions() logging.info('Number of Functions: {}'.format(number_of_functions)) number_of_names = self.get_number_of_names() logging.info('Number of Names: {}'.format(number_of_names)) exported_functions_by_name = self.get_pe_dll_exported_functions() logging.info('PE Exported Functions By Name: {}'.format( ', '.join(exported_functions_by_name))) else: logging.info('This DLL does not have exported functions') def get_pe_dll_exported_functions(self): exported_functions_by_name = self.get_exported_functions_by_name() return exported_functions_by_name def get_export_descriptor_directory_foff(self): if self.export_directory_foff: return self.export_directory_foff export_directory_rva = self.data_directory_info[0]['rva'] return self._rva_to_file_offset(export_directory_rva) def has_exports(self): return self.data_directory_info[0]['rva'] != 0 def get_number_of_functions(self): number_of_functions_off = 0x14 export_directory_foff = self.get_export_descriptor_directory_foff() number_of_functions = self._unpack_dword( self.bin_contents, export_directory_foff + number_of_functions_off) return number_of_functions def get_number_of_names(self): number_of_names_off = 0x18 export_directory_foff = self.get_export_descriptor_directory_foff() number_of_names = self._unpack_dword( self.bin_contents, export_directory_foff + number_of_names_off) return number_of_names def get_exported_functions_by_name(self): exported_functions = [] addr_of_names_foff = self.get_addr_of_names_foff() number_of_names = self.get_number_of_names() if number_of_names: for idx in range(number_of_names): exported_name_rva = self._unpack_dword(self.bin_contents, addr_of_names_foff + idx * 4) # 4 = DWORD exported_name_foff = self._rva_to_file_offset( exported_name_rva) exported_name_str = self._read_null_terminated_ascii_string( exported_name_foff) exported_functions.append(exported_name_str) return exported_functions def get_addr_of_names_foff(self): addr_of_names_offs = 0x20 export_directory_foff = self.get_export_descriptor_directory_foff() addr_of_names_rva = self._unpack_dword( self.bin_contents, export_directory_foff + addr_of_names_offs) addr_of_names_foff = self._rva_to_file_offset(addr_of_names_rva) return addr_of_names_foff def _read_null_terminated_ascii_string(self, starting_addr): idx = 0 ascii_string = '' while ord(self.bin_contents[starting_addr + idx]) != 0 and starting_addr + idx < len( self.bin_contents): ascii_string += self.bin_contents[starting_addr + idx] idx += 1 return ascii_string def _rva_to_file_offset(self, rva): file_offs = -1 for section_info in self.section_headers_info: if self._rva_belongs_to_section(rva, section_info): file_offs = rva - section_info[ 'Virtual Address'] + section_info['Pointer to Raw Data'] break return file_offs @staticmethod def _rva_belongs_to_section(rva, section_info): return section_info['Virtual Address'] <= rva < section_info[ 'Virtual Address'] + section_info['Virtual Size'] @staticmethod def _unpack_dword(contents, offs): # format string: <: little endian. L: 4 bytes (1 dword) return struct.unpack_from('<L', contents, offs)[0]
class ParsePE32(object): def __init__(self): self.bin_contents = None self.section_headers_info = None self.data_directory_info = None self.pe_section_utils = None self.pe_data_directory_utils = None self.image_base_relocation_foff = None def execute(self, bin_path): self.bin_contents = FileUtils.read_file(bin_path) if self.bin_contents: self.parse_pe32() def parse_pe32(self): self.pe_section_utils = PESectionUtils(self.bin_contents) self.section_headers_info = self.pe_section_utils.get_sections_info() self.pe_data_directory_utils = PEDataDirectoryUtils(self.bin_contents) self.data_directory_info = self.pe_data_directory_utils.get_data_directories_info() self.image_base_relocation_foff = self.get_image_base_relocation_rva() max_reloc_rva = self.get_max_rva_for_relocs() logging.info('End RVA for relocation information: {}'.format(max_reloc_rva)) relocation_rvas = self.get_relocation_rvas(max_reloc_rva) logging.info('Virtual Addresses where relocations should be applied ({} relocations):'.format(len(relocation_rvas))) for reloc_rva in relocation_rvas: logging.info(' 0x{:x}'.format(reloc_rva)) def get_image_base_relocation_rva(self): if self.image_base_relocation_foff: return self.image_base_relocation_foff image_base_relocation_rva = self.data_directory_info[5]['rva'] return image_base_relocation_rva def get_max_rva_for_relocs(self): return self.get_image_base_relocation_rva() + self.data_directory_info[5]['size'] def get_relocation_rvas(self, max_reloc_rva): relocation_rvas = [] block_rva = self.get_image_base_relocation_rva() while block_rva < max_reloc_rva: reloc_rvas_for_block = self.get_relocations_for_block(block_rva) relocation_rvas += reloc_rvas_for_block block_rva += self.get_block_size(block_rva) return relocation_rvas def get_relocations_for_block(self, block_rva): block_relocs = [] block_foff = PEGenericUtils.rva_to_file_offset(self.section_headers_info, block_rva) block_virtualaddress = self.get_block_virtualaddress(block_rva) size_of_block = self.get_block_size(block_rva) relocation_block_foff, relocation_foff = block_foff, block_foff + 8 while relocation_foff < relocation_block_foff + size_of_block: reloc_value = self.parse_reloc(relocation_foff) if reloc_value: block_relocs.append(block_virtualaddress + reloc_value) relocation_foff += 2 return block_relocs def get_block_virtualaddress(self, block_rva): virtualaddress_off = 0x0 block_foff = PEGenericUtils.rva_to_file_offset(self.section_headers_info, block_rva) size_of_block = PEGenericUtils.unpack_dword(self.bin_contents, block_foff + virtualaddress_off) return size_of_block def get_block_size(self, block_rva): size_of_block_off = 0x4 block_foff = PEGenericUtils.rva_to_file_offset(self.section_headers_info, block_rva) size_of_block = PEGenericUtils.unpack_dword(self.bin_contents, block_foff + size_of_block_off) return size_of_block def parse_reloc(self, reloc_foff): reloc_type_mask = 0b1111000000000000 reloc_value_mask = 0b0000111111111111 reloc_word = PEGenericUtils.unpack_word(self.bin_contents, reloc_foff) reloc_type = (reloc_word & reloc_type_mask) >> 12 reloc_value = reloc_word & reloc_value_mask if reloc_type == 0x3 else None return reloc_value