// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/* Package pe implements access to PE (Microsoft Windows Portable Executable) files. # Security This package is not designed to be hardened against adversarial inputs, and is outside the scope of https://go.dev/security/policy. In particular, only basic validation is done when parsing object files. As such, care should be taken when parsing untrusted inputs, as parsing malformed files may consume significant resources, or cause panics. */
package pe import ( ) // A File represents an open PE file. type File struct { FileHeader OptionalHeader any // of type *OptionalHeader32 or *OptionalHeader64 Sections []*Section Symbols []*Symbol // COFF symbols with auxiliary symbol records removed COFFSymbols []COFFSymbol // all COFF symbols (including auxiliary symbol records) StringTable StringTable closer io.Closer } // Open opens the named file using [os.Open] and prepares it for use as a PE binary. func ( string) (*File, error) { , := os.Open() if != nil { return nil, } , := NewFile() if != nil { .Close() return nil, } .closer = return , nil } // Close closes the [File]. // If the [File] was created using [NewFile] directly instead of [Open], // Close has no effect. func ( *File) () error { var error if .closer != nil { = .closer.Close() .closer = nil } return } // TODO(brainman): add Load function, as a replacement for NewFile, that does not call removeAuxSymbols (for performance) // NewFile creates a new [File] for accessing a PE binary in an underlying reader. func ( io.ReaderAt) (*File, error) { := new(File) := io.NewSectionReader(, 0, 1<<63-1) var [96]byte if , := .ReadAt([0:], 0); != nil { return nil, } var int64 if [0] == 'M' && [1] == 'Z' { := int64(binary.LittleEndian.Uint32([0x3c:])) var [4]byte .ReadAt([:], ) if !([0] == 'P' && [1] == 'E' && [2] == 0 && [3] == 0) { return nil, fmt.Errorf("invalid PE file signature: % x", ) } = + 4 } else { = int64(0) } .Seek(, io.SeekStart) if := binary.Read(, binary.LittleEndian, &.FileHeader); != nil { return nil, } switch .FileHeader.Machine { case IMAGE_FILE_MACHINE_AMD64, IMAGE_FILE_MACHINE_ARM64, IMAGE_FILE_MACHINE_ARMNT, IMAGE_FILE_MACHINE_I386, IMAGE_FILE_MACHINE_RISCV32, IMAGE_FILE_MACHINE_RISCV64, IMAGE_FILE_MACHINE_RISCV128, IMAGE_FILE_MACHINE_UNKNOWN: // ok default: return nil, fmt.Errorf("unrecognized PE machine: %#x", .FileHeader.Machine) } var error // Read string table. .StringTable, = readStringTable(&.FileHeader, ) if != nil { return nil, } // Read symbol table. .COFFSymbols, = readCOFFSymbols(&.FileHeader, ) if != nil { return nil, } .Symbols, = removeAuxSymbols(.COFFSymbols, .StringTable) if != nil { return nil, } // Seek past file header. _, = .Seek(+int64(binary.Size(.FileHeader)), io.SeekStart) if != nil { return nil, } // Read optional header. .OptionalHeader, = readOptionalHeader(, .FileHeader.SizeOfOptionalHeader) if != nil { return nil, } // Process sections. .Sections = make([]*Section, .FileHeader.NumberOfSections) for := 0; < int(.FileHeader.NumberOfSections); ++ { := new(SectionHeader32) if := binary.Read(, binary.LittleEndian, ); != nil { return nil, } , := .fullName(.StringTable) if != nil { return nil, } := new(Section) .SectionHeader = SectionHeader{ Name: , VirtualSize: .VirtualSize, VirtualAddress: .VirtualAddress, Size: .SizeOfRawData, Offset: .PointerToRawData, PointerToRelocations: .PointerToRelocations, PointerToLineNumbers: .PointerToLineNumbers, NumberOfRelocations: .NumberOfRelocations, NumberOfLineNumbers: .NumberOfLineNumbers, Characteristics: .Characteristics, } := if .PointerToRawData == 0 { // .bss must have all 0s = &nobitsSectionReader{} } .sr = io.NewSectionReader(, int64(.SectionHeader.Offset), int64(.SectionHeader.Size)) .ReaderAt = .sr .Sections[] = } for := range .Sections { var error .Sections[].Relocs, = readRelocs(&.Sections[].SectionHeader, ) if != nil { return nil, } } return , nil } type nobitsSectionReader struct{} func (*nobitsSectionReader) ( []byte, int64) ( int, error) { return 0, errors.New("unexpected read from section with uninitialized data") } // getString extracts a string from symbol string table. func getString( []byte, int) (string, bool) { if < 0 || >= len() { return "", false } for := ; < len(); ++ { if [] == 0 { return string([:]), true } } return "", false } // Section returns the first section with the given name, or nil if no such // section exists. func ( *File) ( string) *Section { for , := range .Sections { if .Name == { return } } return nil } func ( *File) () (*dwarf.Data, error) { := func( *Section) string { switch { case strings.HasPrefix(.Name, ".debug_"): return .Name[7:] case strings.HasPrefix(.Name, ".zdebug_"): return .Name[8:] default: return "" } } // sectionData gets the data for s and checks its size. := func( *Section) ([]byte, error) { , := .Data() if != nil && uint32(len()) < .Size { return nil, } if 0 < .VirtualSize && .VirtualSize < .Size { = [:.VirtualSize] } if len() >= 12 && string([:4]) == "ZLIB" { := binary.BigEndian.Uint64([4:12]) := make([]byte, ) , := zlib.NewReader(bytes.NewBuffer([12:])) if != nil { return nil, } if , := io.ReadFull(, ); != nil { return nil, } if := .Close(); != nil { return nil, } = } return , nil } // There are many other DWARF sections, but these // are the ones the debug/dwarf package uses. // Don't bother loading others. var = map[string][]byte{"abbrev": nil, "info": nil, "str": nil, "line": nil, "ranges": nil} for , := range .Sections { := () if == "" { continue } if , := []; ! { continue } , := () if != nil { return nil, } [] = } , := dwarf.New(["abbrev"], nil, nil, ["info"], ["line"], nil, ["ranges"], ["str"]) if != nil { return nil, } // Look for DWARF4 .debug_types sections and DWARF5 sections. for , := range .Sections { := () if == "" { continue } if , := []; { // Already handled. continue } , := () if != nil { return nil, } if == "types" { = .AddTypes(fmt.Sprintf("types-%d", ), ) } else { = .AddSection(".debug_"+, ) } if != nil { return nil, } } return , nil } // TODO(brainman): document ImportDirectory once we decide what to do with it. type ImportDirectory struct { OriginalFirstThunk uint32 TimeDateStamp uint32 ForwarderChain uint32 Name uint32 FirstThunk uint32 dll string } // ImportedSymbols returns the names of all symbols // referred to by the binary f that are expected to be // satisfied by other libraries at dynamic load time. // It does not return weak symbols. func ( *File) () ([]string, error) { if .OptionalHeader == nil { return nil, nil } , := .OptionalHeader.(*OptionalHeader64) // grab the number of data directory entries var uint32 if { = .OptionalHeader.(*OptionalHeader64).NumberOfRvaAndSizes } else { = .OptionalHeader.(*OptionalHeader32).NumberOfRvaAndSizes } // check that the length of data directory entries is large // enough to include the imports directory. if < IMAGE_DIRECTORY_ENTRY_IMPORT+1 { return nil, nil } // grab the import data directory entry var DataDirectory if { = .OptionalHeader.(*OptionalHeader64).DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] } else { = .OptionalHeader.(*OptionalHeader32).DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] } // figure out which section contains the import directory table var *Section = nil for , := range .Sections { if .Offset == 0 { continue } // We are using distance between s.VirtualAddress and idd.VirtualAddress // to avoid potential overflow of uint32 caused by addition of s.VirtualSize // to s.VirtualAddress. if .VirtualAddress <= .VirtualAddress && .VirtualAddress-.VirtualAddress < .VirtualSize { = break } } // didn't find a section, so no import libraries were found if == nil { return nil, nil } , := .Data() if != nil { return nil, } // seek to the virtual address specified in the import data directory = [.VirtualAddress-.VirtualAddress:] // start decoding the import directory var []ImportDirectory for len() >= 20 { var ImportDirectory .OriginalFirstThunk = binary.LittleEndian.Uint32([0:4]) .TimeDateStamp = binary.LittleEndian.Uint32([4:8]) .ForwarderChain = binary.LittleEndian.Uint32([8:12]) .Name = binary.LittleEndian.Uint32([12:16]) .FirstThunk = binary.LittleEndian.Uint32([16:20]) = [20:] if .OriginalFirstThunk == 0 { break } = append(, ) } // TODO(brainman): this needs to be rewritten // ds.Data() returns contents of section containing import table. Why store in variable called "names"? // Why we are retrieving it second time? We already have it in "d", and it is not modified anywhere. // getString does not extracts a string from symbol string table (as getString doco says). // Why ds.Data() called again and again in the loop? // Needs test before rewrite. , := .Data() var []string for , := range { .dll, _ = getString(, int(.Name-.VirtualAddress)) , _ = .Data() // seek to OriginalFirstThunk = [.OriginalFirstThunk-.VirtualAddress:] for len() > 0 { if { // 64bit := binary.LittleEndian.Uint64([0:8]) = [8:] if == 0 { break } if &0x8000000000000000 > 0 { // is Ordinal // TODO add dynimport ordinal support. } else { , := getString(, int(uint32()-.VirtualAddress+2)) = append(, +":"+.dll) } } else { // 32bit := binary.LittleEndian.Uint32([0:4]) = [4:] if == 0 { break } if &0x80000000 > 0 { // is Ordinal // TODO add dynimport ordinal support. //ord := va&0x0000FFFF } else { , := getString(, int(-.VirtualAddress+2)) = append(, +":"+.dll) } } } } return , nil } // ImportedLibraries returns the names of all libraries // referred to by the binary f that are expected to be // linked with the binary at dynamic link time. func ( *File) () ([]string, error) { // TODO // cgo -dynimport don't use this for windows PE, so just return. return nil, nil } // FormatError is unused. // The type is retained for compatibility. type FormatError struct { } func ( *FormatError) () string { return "unknown error" } // readOptionalHeader accepts an io.ReadSeeker pointing to optional header in the PE file // and its size as seen in the file header. // It parses the given size of bytes and returns optional header. It infers whether the // bytes being parsed refer to 32 bit or 64 bit version of optional header. func readOptionalHeader( io.ReadSeeker, uint16) (any, error) { // If optional header size is 0, return empty optional header. if == 0 { return nil, nil } var ( // First couple of bytes in option header state its type. // We need to read them first to determine the type and // validity of optional header. uint16 = binary.Size() ) // If optional header size is greater than 0 but less than its magic size, return error. if < uint16() { return nil, fmt.Errorf("optional header size is less than optional header magic size") } // read reads from io.ReadSeeke, r, into data. var error := func( any) bool { = binary.Read(, binary.LittleEndian, ) return == nil } if !(&) { return nil, fmt.Errorf("failure to read optional header magic: %v", ) } switch { case 0x10b: // PE32 var ( OptionalHeader32 // There can be 0 or more data directories. So the minimum size of optional // header is calculated by subtracting oh32.DataDirectory size from oh32 size. = binary.Size() - binary.Size(.DataDirectory) ) if < uint16() { return nil, fmt.Errorf("optional header size(%d) is less minimum size (%d) of PE32 optional header", , ) } // Init oh32 fields .Magic = if !(&.MajorLinkerVersion) || !(&.MinorLinkerVersion) || !(&.SizeOfCode) || !(&.SizeOfInitializedData) || !(&.SizeOfUninitializedData) || !(&.AddressOfEntryPoint) || !(&.BaseOfCode) || !(&.BaseOfData) || !(&.ImageBase) || !(&.SectionAlignment) || !(&.FileAlignment) || !(&.MajorOperatingSystemVersion) || !(&.MinorOperatingSystemVersion) || !(&.MajorImageVersion) || !(&.MinorImageVersion) || !(&.MajorSubsystemVersion) || !(&.MinorSubsystemVersion) || !(&.Win32VersionValue) || !(&.SizeOfImage) || !(&.SizeOfHeaders) || !(&.CheckSum) || !(&.Subsystem) || !(&.DllCharacteristics) || !(&.SizeOfStackReserve) || !(&.SizeOfStackCommit) || !(&.SizeOfHeapReserve) || !(&.SizeOfHeapCommit) || !(&.LoaderFlags) || !(&.NumberOfRvaAndSizes) { return nil, fmt.Errorf("failure to read PE32 optional header: %v", ) } , := readDataDirectories(, -uint16(), .NumberOfRvaAndSizes) if != nil { return nil, } copy(.DataDirectory[:], ) return &, nil case 0x20b: // PE32+ var ( OptionalHeader64 // There can be 0 or more data directories. So the minimum size of optional // header is calculated by subtracting oh64.DataDirectory size from oh64 size. = binary.Size() - binary.Size(.DataDirectory) ) if < uint16() { return nil, fmt.Errorf("optional header size(%d) is less minimum size (%d) for PE32+ optional header", , ) } // Init oh64 fields .Magic = if !(&.MajorLinkerVersion) || !(&.MinorLinkerVersion) || !(&.SizeOfCode) || !(&.SizeOfInitializedData) || !(&.SizeOfUninitializedData) || !(&.AddressOfEntryPoint) || !(&.BaseOfCode) || !(&.ImageBase) || !(&.SectionAlignment) || !(&.FileAlignment) || !(&.MajorOperatingSystemVersion) || !(&.MinorOperatingSystemVersion) || !(&.MajorImageVersion) || !(&.MinorImageVersion) || !(&.MajorSubsystemVersion) || !(&.MinorSubsystemVersion) || !(&.Win32VersionValue) || !(&.SizeOfImage) || !(&.SizeOfHeaders) || !(&.CheckSum) || !(&.Subsystem) || !(&.DllCharacteristics) || !(&.SizeOfStackReserve) || !(&.SizeOfStackCommit) || !(&.SizeOfHeapReserve) || !(&.SizeOfHeapCommit) || !(&.LoaderFlags) || !(&.NumberOfRvaAndSizes) { return nil, fmt.Errorf("failure to read PE32+ optional header: %v", ) } , := readDataDirectories(, -uint16(), .NumberOfRvaAndSizes) if != nil { return nil, } copy(.DataDirectory[:], ) return &, nil default: return nil, fmt.Errorf("optional header has unexpected Magic of 0x%x", ) } } // readDataDirectories accepts an io.ReadSeeker pointing to data directories in the PE file, // its size and number of data directories as seen in optional header. // It parses the given size of bytes and returns given number of data directories. func readDataDirectories( io.ReadSeeker, uint16, uint32) ([]DataDirectory, error) { := uint64(binary.Size(DataDirectory{})) if uint64() != uint64()* { return nil, fmt.Errorf("size of data directories(%d) is inconsistent with number of data directories(%d)", , ) } := make([]DataDirectory, ) if := binary.Read(, binary.LittleEndian, ); != nil { return nil, fmt.Errorf("failure to read data directories: %v", ) } return , nil }