github.com/saferwall/pe@v1.5.2/file.go (about)

     1  // Copyright 2018 Saferwall. All rights reserved.
     2  // Use of this source code is governed by Apache v2 license
     3  // license that can be found in the LICENSE file.
     4  
     5  package pe
     6  
     7  import (
     8  	"errors"
     9  	"os"
    10  
    11  	"github.com/edsrzf/mmap-go"
    12  	"github.com/saferwall/pe/log"
    13  )
    14  
    15  // A File represents an open PE file.
    16  type File struct {
    17  	DOSHeader    ImageDOSHeader              `json:"dos_header,omitempty"`
    18  	RichHeader   RichHeader                  `json:"rich_header,omitempty"`
    19  	NtHeader     ImageNtHeader               `json:"nt_header,omitempty"`
    20  	COFF         COFF                        `json:"coff,omitempty"`
    21  	Sections     []Section                   `json:"sections,omitempty"`
    22  	Imports      []Import                    `json:"imports,omitempty"`
    23  	Export       Export                      `json:"export,omitempty"`
    24  	Debugs       []DebugEntry                `json:"debugs,omitempty"`
    25  	Relocations  []Relocation                `json:"relocations,omitempty"`
    26  	Resources    ResourceDirectory           `json:"resources,omitempty"`
    27  	TLS          TLSDirectory                `json:"tls,omitempty"`
    28  	LoadConfig   LoadConfig                  `json:"load_config,omitempty"`
    29  	Exceptions   []Exception                 `json:"exceptions,omitempty"`
    30  	Certificates Certificate                 `json:"certificates,omitempty"`
    31  	DelayImports []DelayImport               `json:"delay_imports,omitempty"`
    32  	BoundImports []BoundImportDescriptorData `json:"bound_imports,omitempty"`
    33  	GlobalPtr    uint32                      `json:"global_ptr,omitempty"`
    34  	CLR          CLRData                     `json:"clr,omitempty"`
    35  	IAT          []IATEntry                  `json:"iat,omitempty"`
    36  	Anomalies    []string                    `json:"anomalies,omitempty"`
    37  	Header       []byte
    38  	data         mmap.MMap
    39  	FileInfo
    40  	size          uint32
    41  	OverlayOffset int64
    42  	f             *os.File
    43  	opts          *Options
    44  	logger        *log.Helper
    45  }
    46  
    47  // Options that influence the PE parsing behaviour.
    48  type Options struct {
    49  
    50  	// Parse only the PE header and do not parse data directories, by default (false).
    51  	Fast bool
    52  
    53  	// Includes section entropy, by default (false).
    54  	SectionEntropy bool
    55  
    56  	// Maximum COFF symbols to parse, by default (MaxDefaultCOFFSymbolsCount).
    57  	MaxCOFFSymbolsCount uint32
    58  
    59  	// Maximum relocations to parse, by default (MaxDefaultRelocEntriesCount).
    60  	MaxRelocEntriesCount uint32
    61  
    62  	// Disable certificate validation, by default (false).
    63  	DisableCertValidation bool
    64  
    65  	// Disable signature validation, by default (false).
    66  	DisableSignatureValidation bool
    67  
    68  	// A custom logger.
    69  	Logger log.Logger
    70  
    71  	// OmitExportDirectory determines if export directory parsing is skipped, by default (false).
    72  	OmitExportDirectory bool
    73  
    74  	// OmitImportDirectory determines if import directory parsing is skipped, by default (false).
    75  	OmitImportDirectory bool
    76  
    77  	// OmitExceptionDirectory determines if exception directory parsing is skipped, by default (false).
    78  	OmitExceptionDirectory bool
    79  
    80  	// OmitResourceDirectory determines if resource directory parsing is skipped, by default (false).
    81  	OmitResourceDirectory bool
    82  
    83  	// OmitSecurityDirectory determines if security directory parsing is skipped, by default (false).
    84  	OmitSecurityDirectory bool
    85  
    86  	// OmitRelocDirectory determines if relocation directory parsing is skipped, by default (false).
    87  	OmitRelocDirectory bool
    88  
    89  	// OmitDebugDirectory determines if debug directory parsing is skipped, by default (false).
    90  	OmitDebugDirectory bool
    91  
    92  	// OmitArchitectureDirectory determines if architecture directory parsing is skipped, by default (false).
    93  	OmitArchitectureDirectory bool
    94  
    95  	// OmitGlobalPtrDirectory determines if global pointer directory parsing is skipped, by default (false).
    96  	OmitGlobalPtrDirectory bool
    97  
    98  	// OmitTLSDirectory determines if TLS directory parsing is skipped, by default (false).
    99  	OmitTLSDirectory bool
   100  
   101  	// OmitLoadConfigDirectory determines if load config directory parsing is skipped, by default (false).
   102  	OmitLoadConfigDirectory bool
   103  
   104  	// OmitBoundImportDirectory determines if bound import directory parsing is skipped, by default (false).
   105  	OmitBoundImportDirectory bool
   106  
   107  	// OmitIATDirectory determines if IAT directory parsing is skipped, by default (false).
   108  	OmitIATDirectory bool
   109  
   110  	// OmitDelayImportDirectory determines if delay import directory parsing is skipped, by default (false).
   111  	OmitDelayImportDirectory bool
   112  
   113  	// OmitCLRHeaderDirectory determines if CLR header directory parsing is skipped, by default (false).
   114  	OmitCLRHeaderDirectory bool
   115  }
   116  
   117  // New instantiates a file instance with options given a file name.
   118  func New(name string, opts *Options) (*File, error) {
   119  
   120  	f, err := os.Open(name)
   121  	if err != nil {
   122  		return nil, err
   123  	}
   124  
   125  	// Memory map the file instead of using read/write.
   126  	data, err := mmap.Map(f, mmap.RDONLY, 0)
   127  	if err != nil {
   128  		f.Close()
   129  		return nil, err
   130  	}
   131  
   132  	file := File{}
   133  	if opts != nil {
   134  		file.opts = opts
   135  	} else {
   136  		file.opts = &Options{}
   137  	}
   138  
   139  	if file.opts.MaxCOFFSymbolsCount == 0 {
   140  		file.opts.MaxCOFFSymbolsCount = MaxDefaultCOFFSymbolsCount
   141  	}
   142  	if file.opts.MaxRelocEntriesCount == 0 {
   143  		file.opts.MaxRelocEntriesCount = MaxDefaultRelocEntriesCount
   144  	}
   145  
   146  	var logger log.Logger
   147  	if opts.Logger == nil {
   148  		logger = log.NewStdLogger(os.Stdout)
   149  		file.logger = log.NewHelper(log.NewFilter(logger,
   150  			log.FilterLevel(log.LevelError)))
   151  	} else {
   152  		file.logger = log.NewHelper(opts.Logger)
   153  	}
   154  
   155  	file.data = data
   156  	file.size = uint32(len(file.data))
   157  	file.f = f
   158  	return &file, nil
   159  }
   160  
   161  // NewBytes instantiates a file instance with options given a memory buffer.
   162  func NewBytes(data []byte, opts *Options) (*File, error) {
   163  
   164  	file := File{}
   165  	if opts != nil {
   166  		file.opts = opts
   167  	} else {
   168  		file.opts = &Options{}
   169  	}
   170  
   171  	if file.opts.MaxCOFFSymbolsCount == 0 {
   172  		file.opts.MaxCOFFSymbolsCount = MaxDefaultCOFFSymbolsCount
   173  	}
   174  	if file.opts.MaxRelocEntriesCount == 0 {
   175  		file.opts.MaxRelocEntriesCount = MaxDefaultRelocEntriesCount
   176  	}
   177  
   178  	var logger log.Logger
   179  	if opts.Logger == nil {
   180  		logger = log.NewStdLogger(os.Stdout)
   181  		file.logger = log.NewHelper(log.NewFilter(logger,
   182  			log.FilterLevel(log.LevelError)))
   183  	} else {
   184  		file.logger = log.NewHelper(opts.Logger)
   185  	}
   186  
   187  	file.data = data
   188  	file.size = uint32(len(file.data))
   189  	return &file, nil
   190  }
   191  
   192  // Close closes the File.
   193  func (pe *File) Close() error {
   194  	if pe.data != nil {
   195  		_ = pe.data.Unmap()
   196  	}
   197  
   198  	if pe.f != nil {
   199  		return pe.f.Close()
   200  	}
   201  	return nil
   202  }
   203  
   204  // Parse performs the file parsing for a PE binary.
   205  func (pe *File) Parse() error {
   206  
   207  	// check for the smallest PE size.
   208  	if len(pe.data) < TinyPESize {
   209  		return ErrInvalidPESize
   210  	}
   211  
   212  	// Parse the DOS header.
   213  	err := pe.ParseDOSHeader()
   214  	if err != nil {
   215  		return err
   216  	}
   217  
   218  	// Parse the Rich header.
   219  	err = pe.ParseRichHeader()
   220  	if err != nil {
   221  		pe.logger.Errorf("rich header parsing failed: %v", err)
   222  	}
   223  
   224  	// Parse the NT header.
   225  	err = pe.ParseNTHeader()
   226  	if err != nil {
   227  		return err
   228  	}
   229  
   230  	// Parse COFF symbol table.
   231  	err = pe.ParseCOFFSymbolTable()
   232  	if err != nil {
   233  		pe.logger.Debugf("coff symbols parsing failed: %v", err)
   234  	}
   235  
   236  	// Parse the Section Header.
   237  	err = pe.ParseSectionHeader()
   238  	if err != nil {
   239  		return err
   240  	}
   241  
   242  	// In fast mode, do not parse data directories.
   243  	if pe.opts.Fast {
   244  		return nil
   245  	}
   246  
   247  	// Parse the Data Directory entries.
   248  	return pe.ParseDataDirectories()
   249  }
   250  
   251  // String stringify the data directory entry.
   252  func (entry ImageDirectoryEntry) String() string {
   253  	dataDirMap := map[ImageDirectoryEntry]string{
   254  		ImageDirectoryEntryExport:       "Export",
   255  		ImageDirectoryEntryImport:       "Import",
   256  		ImageDirectoryEntryResource:     "Resource",
   257  		ImageDirectoryEntryException:    "Exception",
   258  		ImageDirectoryEntryCertificate:  "Security",
   259  		ImageDirectoryEntryBaseReloc:    "Relocation",
   260  		ImageDirectoryEntryDebug:        "Debug",
   261  		ImageDirectoryEntryArchitecture: "Architecture",
   262  		ImageDirectoryEntryGlobalPtr:    "GlobalPtr",
   263  		ImageDirectoryEntryTLS:          "TLS",
   264  		ImageDirectoryEntryLoadConfig:   "LoadConfig",
   265  		ImageDirectoryEntryBoundImport:  "BoundImport",
   266  		ImageDirectoryEntryIAT:          "IAT",
   267  		ImageDirectoryEntryDelayImport:  "DelayImport",
   268  		ImageDirectoryEntryCLR:          "CLR",
   269  		ImageDirectoryEntryReserved:     "Reserved",
   270  	}
   271  
   272  	return dataDirMap[entry]
   273  }
   274  
   275  // ParseDataDirectories parses the data directories. The DataDirectory is an
   276  // array of 16 structures. Each array entry has a predefined meaning for what
   277  // it refers to.
   278  func (pe *File) ParseDataDirectories() error {
   279  
   280  	foundErr := false
   281  	oh32 := ImageOptionalHeader32{}
   282  	oh64 := ImageOptionalHeader64{}
   283  
   284  	switch pe.Is64 {
   285  	case true:
   286  		oh64 = pe.NtHeader.OptionalHeader.(ImageOptionalHeader64)
   287  	case false:
   288  		oh32 = pe.NtHeader.OptionalHeader.(ImageOptionalHeader32)
   289  	}
   290  
   291  	// Maps data directory index to function which parses that directory.
   292  	funcMaps := make(map[ImageDirectoryEntry]func(uint32, uint32) error)
   293  	if !pe.opts.OmitExportDirectory {
   294  		funcMaps[ImageDirectoryEntryExport] = pe.parseExportDirectory
   295  	}
   296  	if !pe.opts.OmitImportDirectory {
   297  		funcMaps[ImageDirectoryEntryImport] = pe.parseImportDirectory
   298  	}
   299  	if !pe.opts.OmitExceptionDirectory {
   300  		funcMaps[ImageDirectoryEntryException] = pe.parseExceptionDirectory
   301  	}
   302  	if !pe.opts.OmitResourceDirectory {
   303  		funcMaps[ImageDirectoryEntryResource] = pe.parseResourceDirectory
   304  	}
   305  	if !pe.opts.OmitSecurityDirectory {
   306  		funcMaps[ImageDirectoryEntryCertificate] = pe.parseSecurityDirectory
   307  	}
   308  	if !pe.opts.OmitRelocDirectory {
   309  		funcMaps[ImageDirectoryEntryBaseReloc] = pe.parseRelocDirectory
   310  	}
   311  	if !pe.opts.OmitDebugDirectory {
   312  		funcMaps[ImageDirectoryEntryDebug] = pe.parseDebugDirectory
   313  	}
   314  	if !pe.opts.OmitArchitectureDirectory {
   315  		funcMaps[ImageDirectoryEntryArchitecture] = pe.parseArchitectureDirectory
   316  	}
   317  	if !pe.opts.OmitGlobalPtrDirectory {
   318  		funcMaps[ImageDirectoryEntryGlobalPtr] = pe.parseGlobalPtrDirectory
   319  	}
   320  	if !pe.opts.OmitTLSDirectory {
   321  		funcMaps[ImageDirectoryEntryTLS] = pe.parseTLSDirectory
   322  	}
   323  	if !pe.opts.OmitLoadConfigDirectory {
   324  		funcMaps[ImageDirectoryEntryLoadConfig] = pe.parseLoadConfigDirectory
   325  	}
   326  	if !pe.opts.OmitBoundImportDirectory {
   327  		funcMaps[ImageDirectoryEntryBoundImport] = pe.parseBoundImportDirectory
   328  	}
   329  	if !pe.opts.OmitIATDirectory {
   330  		funcMaps[ImageDirectoryEntryIAT] = pe.parseIATDirectory
   331  	}
   332  	if !pe.opts.OmitDelayImportDirectory {
   333  		funcMaps[ImageDirectoryEntryDelayImport] = pe.parseDelayImportDirectory
   334  	}
   335  	if !pe.opts.OmitCLRHeaderDirectory {
   336  		funcMaps[ImageDirectoryEntryCLR] = pe.parseCLRHeaderDirectory
   337  	}
   338  
   339  	// Iterate over data directories and call the appropriate function.
   340  	for entryIndex := ImageDirectoryEntry(0); entryIndex < ImageNumberOfDirectoryEntries; entryIndex++ {
   341  
   342  		var va, size uint32
   343  		switch pe.Is64 {
   344  		case true:
   345  			dirEntry := oh64.DataDirectory[entryIndex]
   346  			va = dirEntry.VirtualAddress
   347  			size = dirEntry.Size
   348  		case false:
   349  			dirEntry := oh32.DataDirectory[entryIndex]
   350  			va = dirEntry.VirtualAddress
   351  			size = dirEntry.Size
   352  		}
   353  
   354  		if va != 0 {
   355  			func() {
   356  				// keep parsing data directories even though some entries fails.
   357  				defer func() {
   358  					if e := recover(); e != nil {
   359  						pe.logger.Errorf("unhandled exception when parsing data directory %s, reason: %v",
   360  							entryIndex.String(), e)
   361  						foundErr = true
   362  					}
   363  				}()
   364  
   365  				// the last entry in the data directories is reserved and must be zero.
   366  				if entryIndex == ImageDirectoryEntryReserved {
   367  					pe.Anomalies = append(pe.Anomalies, AnoReservedDataDirectoryEntry)
   368  					return
   369  				}
   370  
   371  				parseDirectory, ok := funcMaps[entryIndex]
   372  				if !ok {
   373  					return
   374  				}
   375  				err := parseDirectory(va, size)
   376  				if err != nil {
   377  					pe.logger.Warnf("failed to parse data directory %s, reason: %v",
   378  						entryIndex.String(), err)
   379  				}
   380  			}()
   381  		}
   382  	}
   383  
   384  	if foundErr {
   385  		return errors.New("Data directory parsing failed")
   386  	}
   387  	return nil
   388  }