github.com/saferwall/pe@v1.5.2/exports.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  	"encoding/binary"
     9  	"errors"
    10  	"fmt"
    11  )
    12  
    13  const (
    14  	maxExportedSymbols = 0x2000
    15  )
    16  
    17  var (
    18  	ErrExportMaxOrdEntries       = "Export directory contains more than max ordinal entries"
    19  	ErrExportManyRepeatedEntries = "Export directory contains many repeated entries"
    20  	AnoNullNumberOfFunctions     = "Export directory contains zero number of functions"
    21  	AnoNullAddressOfFunctions    = "Export directory contains zero address of functions"
    22  )
    23  
    24  // ImageExportDirectory represents the IMAGE_EXPORT_DIRECTORY structure.
    25  // The export directory table contains address information that is used
    26  // to resolve imports to the entry points within this image.
    27  type ImageExportDirectory struct {
    28  	// Reserved, must be 0.
    29  	Characteristics uint32 `json:"characteristics"`
    30  
    31  	// The time and date that the export data was created.
    32  	TimeDateStamp uint32 `json:"time_date_stamp"`
    33  
    34  	// The major version number.
    35  	//The major and minor version numbers can be set by the user.
    36  	MajorVersion uint16 `json:"major_version"`
    37  
    38  	// The minor version number.
    39  	MinorVersion uint16 `json:"minor_version"`
    40  
    41  	// The address of the ASCII string that contains the name of the DLL.
    42  	// This address is relative to the image base.
    43  	Name uint32 `json:"name"`
    44  
    45  	// The starting ordinal number for exports in this image. This field
    46  	// specifies the starting ordinal number for the export address table.
    47  	// It is usually set to 1.
    48  	Base uint32 `json:"base"`
    49  
    50  	// The number of entries in the export address table.
    51  	NumberOfFunctions uint32 `json:"number_of_functions"`
    52  
    53  	// The number of entries in the name pointer table. This is also the number
    54  	// of entries in the ordinal table.
    55  	NumberOfNames uint32 `json:"number_of_names"`
    56  
    57  	// The address of the export address table, relative to the image base.
    58  	AddressOfFunctions uint32 `json:"address_of_functions"`
    59  
    60  	// The address of the export name pointer table, relative to the image base.
    61  	// The table size is given by the Number of Name Pointers field.
    62  	AddressOfNames uint32 `json:"address_of_names"`
    63  
    64  	// The address of the ordinal table, relative to the image base.
    65  	AddressOfNameOrdinals uint32 `json:"address_of_name_ordinals"`
    66  }
    67  
    68  // ExportFunction represents an imported function in the export table.
    69  type ExportFunction struct {
    70  	Ordinal      uint32 `json:"ordinal"`
    71  	FunctionRVA  uint32 `json:"function_rva"`
    72  	NameOrdinal  uint32 `json:"name_ordinal"`
    73  	NameRVA      uint32 `json:"name_rva"`
    74  	Name         string `json:"name"`
    75  	Forwarder    string `json:"forwarder"`
    76  	ForwarderRVA uint32 `json:"forwarder_rva"`
    77  }
    78  
    79  // Export represent the export table.
    80  type Export struct {
    81  	Functions []ExportFunction     `json:"functions"`
    82  	Struct    ImageExportDirectory `json:"struct"`
    83  	Name      string               `json:"name"`
    84  }
    85  
    86  /*
    87  A few notes learned from `Corkami` about parsing export directory:
    88    - like many data directories, Exports' size are not necessary, except for forwarding.
    89    - Characteristics, TimeDateStamp, MajorVersion and MinorVersion are not necessary.
    90    - the export name is not necessary, and can be anything.
    91    - AddressOfNames is lexicographically-ordered.
    92    - export names can have any value (even null or more than 65536 characters long,
    93      with unprintable characters), just null terminated.
    94    - an EXE can have exports (no need of relocation nor DLL flag), and can use
    95  
    96  them normally
    97  - exports can be not used for execution, but for documenting the internal code
    98  - numbers of functions will be different from number of names when the file
    99  is exporting some functions by ordinal.
   100  */
   101  func (pe *File) parseExportDirectory(rva, size uint32) error {
   102  
   103  	// Define some vars.
   104  	exp := Export{}
   105  	exportDir := ImageExportDirectory{}
   106  	errorMsg := fmt.Sprintf("Error parsing export directory at RVA: 0x%x", rva)
   107  
   108  	fileOffset := pe.GetOffsetFromRva(rva)
   109  	exportDirSize := uint32(binary.Size(exportDir))
   110  	err := pe.structUnpack(&exportDir, fileOffset, exportDirSize)
   111  	if err != nil {
   112  		return errors.New(errorMsg)
   113  	}
   114  	exp.Struct = exportDir
   115  
   116  	// We keep track of the bytes left in the file and use it to set a upper
   117  	// bound in the number of items that can be read from the different arrays.
   118  	lengthUntilEOF := func(rva uint32) uint32 {
   119  		return pe.size - pe.GetOffsetFromRva(rva)
   120  	}
   121  	var length uint32
   122  	var addressOfNames []byte
   123  
   124  	// Some DLLs have null number of functions.
   125  	if exportDir.NumberOfFunctions == 0 {
   126  		pe.Anomalies = append(pe.Anomalies, AnoNullNumberOfFunctions)
   127  	}
   128  
   129  	// Some DLLs have null address of functions.
   130  	if exportDir.AddressOfFunctions == 0 {
   131  		pe.Anomalies = append(pe.Anomalies, AnoNullAddressOfFunctions)
   132  	}
   133  
   134  	length = min(lengthUntilEOF(exportDir.AddressOfNames),
   135  		exportDir.NumberOfNames*4)
   136  	addressOfNames, err = pe.GetData(exportDir.AddressOfNames, length)
   137  	if err != nil {
   138  		return errors.New(errorMsg)
   139  	}
   140  
   141  	length = min(lengthUntilEOF(exportDir.AddressOfNameOrdinals),
   142  		exportDir.NumberOfNames*4)
   143  	addressOfNameOrdinals, err := pe.GetData(exportDir.AddressOfNameOrdinals, length)
   144  	if err != nil {
   145  		return errors.New(errorMsg)
   146  	}
   147  
   148  	length = min(lengthUntilEOF(exportDir.AddressOfFunctions),
   149  		exportDir.NumberOfFunctions*4)
   150  	addressOfFunctions, err := pe.GetData(exportDir.AddressOfFunctions, length)
   151  	if err != nil {
   152  		return errors.New(errorMsg)
   153  	}
   154  
   155  	exp.Name = pe.getStringAtRVA(exportDir.Name, 0x100000)
   156  
   157  	maxFailedEntries := 10
   158  	var forwarderStr string
   159  	var forwarderOffset uint32
   160  	safetyBoundary := pe.size // overly generous upper bound
   161  	symbolCounts := make(map[uint32]int)
   162  	parsingFailed := false
   163  
   164  	// read the image export directory
   165  	section := pe.getSectionByRva(exportDir.AddressOfNames)
   166  	if section != nil {
   167  		safetyBoundary = (section.Header.VirtualAddress +
   168  			uint32(len(section.Data(0, 0, pe)))) - exportDir.AddressOfNames
   169  	}
   170  
   171  	numNames := min(exportDir.NumberOfNames, safetyBoundary/4)
   172  	var symbolAddress uint32
   173  	for i := uint32(0); i < numNames; i++ {
   174  
   175  		defer func() {
   176  			// recover from panic if one occured. Set err to nil otherwise.
   177  			if recover() != nil {
   178  				err = errors.New("array index out of bounds")
   179  			}
   180  		}()
   181  
   182  		symbolOrdinal := binary.LittleEndian.Uint16(addressOfNameOrdinals[i*2:])
   183  		symbolAddress = binary.LittleEndian.Uint32(addressOfFunctions[symbolOrdinal*4:])
   184  		if symbolAddress == 0 {
   185  			continue
   186  		}
   187  
   188  		// If the function's RVA points within the export directory
   189  		// it will point to a string with the forwarded symbol's string
   190  		// instead of pointing the the function start address.
   191  		if symbolAddress >= rva && symbolAddress < rva+size {
   192  			forwarderStr = pe.getStringAtRVA(symbolAddress, 0x100000)
   193  			forwarderOffset = pe.GetOffsetFromRva(symbolAddress)
   194  		} else {
   195  			forwarderStr = ""
   196  			fileOffset = 0
   197  		}
   198  
   199  		symbolNameAddress := binary.LittleEndian.Uint32(addressOfNames[i*4:])
   200  		if symbolNameAddress == 0 {
   201  			maxFailedEntries--
   202  			if maxFailedEntries <= 0 {
   203  				parsingFailed = true
   204  				break
   205  			}
   206  		}
   207  		symbolName := pe.getStringAtRVA(symbolNameAddress, 0x100000)
   208  		if !IsValidFunctionName(symbolName) {
   209  			parsingFailed = true
   210  			break
   211  		}
   212  
   213  		symbolNameOffset := pe.GetOffsetFromRva(symbolNameAddress)
   214  		if symbolNameOffset == 0 {
   215  			maxFailedEntries--
   216  			if maxFailedEntries <= 0 {
   217  				parsingFailed = true
   218  				break
   219  			}
   220  		}
   221  
   222  		// File 0b1d3d3664915577ab9a32188d29bbf3542b86c7b9ce333e245496c3018819f1
   223  		// was being parsed as potentially containing millions of exports.
   224  		// Checking for duplicates addresses the issue.
   225  		symbolCounts[symbolAddress]++
   226  		if symbolCounts[symbolAddress] > 10 {
   227  			if !stringInSlice(ErrExportManyRepeatedEntries, pe.Anomalies) {
   228  				pe.Anomalies = append(pe.Anomalies, ErrExportManyRepeatedEntries)
   229  			}
   230  		}
   231  		if len(symbolCounts) > maxExportedSymbols {
   232  			if !stringInSlice(ErrExportMaxOrdEntries, pe.Anomalies) {
   233  				pe.Anomalies = append(pe.Anomalies, ErrExportMaxOrdEntries)
   234  			}
   235  		}
   236  		newExport := ExportFunction{
   237  			Name:         symbolName,
   238  			NameRVA:      symbolNameAddress,
   239  			NameOrdinal:  uint32(symbolOrdinal),
   240  			Ordinal:      exportDir.Base + uint32(symbolOrdinal),
   241  			FunctionRVA:  symbolAddress,
   242  			Forwarder:    forwarderStr,
   243  			ForwarderRVA: forwarderOffset,
   244  		}
   245  
   246  		exp.Functions = append(exp.Functions, newExport)
   247  	}
   248  
   249  	if parsingFailed {
   250  		fmt.Printf("RVA AddressOfNames in the export directory points to an "+
   251  			"invalid address: 0x%x\n", exportDir.AddressOfNames)
   252  	}
   253  
   254  	maxFailedEntries = 10
   255  	section = pe.getSectionByRva(exportDir.AddressOfFunctions)
   256  
   257  	// Overly generous upper bound
   258  	safetyBoundary = pe.size
   259  	if section != nil {
   260  		safetyBoundary = section.Header.VirtualAddress +
   261  			uint32(len(section.Data(0, 0, pe))) - exportDir.AddressOfNames
   262  	}
   263  	parsingFailed = false
   264  	ordinals := make(map[uint32]bool)
   265  	for _, export := range exp.Functions {
   266  		ordinals[export.Ordinal] = true
   267  	}
   268  	numNames = min(exportDir.NumberOfFunctions, safetyBoundary/4)
   269  	for i := uint32(0); i < numNames; i++ {
   270  		value := i + exportDir.Base
   271  		if ordinals[value] {
   272  			continue
   273  		}
   274  
   275  		if len(addressOfFunctions) >= int(i*4)+4 {
   276  			symbolAddress = binary.LittleEndian.Uint32(addressOfFunctions[i*4:])
   277  		}
   278  		if symbolAddress == 0 {
   279  			continue
   280  		}
   281  
   282  		// Checking for forwarder again.
   283  		if symbolAddress >= rva && symbolAddress < rva+size {
   284  			forwarderStr = pe.getStringAtRVA(symbolAddress, 0x100000)
   285  			forwarderOffset = pe.GetOffsetFromRva(symbolAddress)
   286  		} else {
   287  			forwarderStr = ""
   288  			fileOffset = 0
   289  		}
   290  
   291  		// File 0b1d3d3664915577ab9a32188d29bbf3542b86c7b9ce333e245496c3018819f1
   292  		// was being parsed as potentially containing millions of exports.
   293  		// Checking for duplicates addresses the issue.
   294  		symbolCounts[symbolAddress]++
   295  		if symbolCounts[symbolAddress] > 10 {
   296  			if !stringInSlice(ErrExportManyRepeatedEntries, pe.Anomalies) {
   297  				pe.Anomalies = append(pe.Anomalies, ErrExportManyRepeatedEntries)
   298  			}
   299  		}
   300  		if len(symbolCounts) > maxExportedSymbols {
   301  			if !stringInSlice(ErrExportMaxOrdEntries, pe.Anomalies) {
   302  
   303  				pe.Anomalies = append(pe.Anomalies, ErrExportMaxOrdEntries)
   304  			}
   305  		}
   306  		newExport := ExportFunction{
   307  			Ordinal:      exportDir.Base + i,
   308  			FunctionRVA:  symbolAddress,
   309  			Forwarder:    forwarderStr,
   310  			ForwarderRVA: forwarderOffset,
   311  		}
   312  
   313  		exp.Functions = append(exp.Functions, newExport)
   314  	}
   315  
   316  	pe.Export = exp
   317  	pe.HasExport = true
   318  	return nil
   319  }
   320  
   321  // GetExportFunctionByRVA return an export function given an RVA.
   322  func (pe *File) GetExportFunctionByRVA(rva uint32) ExportFunction {
   323  	for _, exp := range pe.Export.Functions {
   324  		if exp.FunctionRVA == rva {
   325  			return exp
   326  		}
   327  	}
   328  
   329  	return ExportFunction{}
   330  }