github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/file/cataloger/executable/pe.go (about)

     1  package executable
     2  
     3  import (
     4  	"debug/pe"
     5  
     6  	"github.com/scylladb/go-set/strset"
     7  
     8  	"github.com/anchore/syft/syft/file"
     9  	"github.com/anchore/syft/syft/internal/unionreader"
    10  )
    11  
    12  func findPEFeatures(data *file.Executable, reader unionreader.UnionReader) error {
    13  	// TODO: support security features
    14  
    15  	f, err := pe.NewFile(reader)
    16  	if err != nil {
    17  		return err
    18  	}
    19  
    20  	libs, err := f.ImportedLibraries()
    21  	if err != nil {
    22  		return err
    23  	}
    24  
    25  	data.ImportedLibraries = libs
    26  	data.HasEntrypoint = peHasEntrypoint(f)
    27  	data.HasExports = peHasExports(f)
    28  
    29  	return nil
    30  }
    31  
    32  var (
    33  	windowsExeEntrypoints = strset.New("main", "WinMain", "wWinMain")
    34  	windowsDllEntrypoints = strset.New("DllMain", "_DllMainCRTStartup@12", "CRT_INIT")
    35  )
    36  
    37  func peHasEntrypoint(f *pe.File) bool {
    38  	// DLLs can have entrypoints, but they are not "executables" in the traditional sense,
    39  	// but instead point to an initialization function (DLLMain).
    40  	// The PE format does not require an entrypoint, so it is possible to not have one, however,
    41  	// the microsoft C runtime does: https://learn.microsoft.com/en-US/troubleshoot/developer/visualstudio/cpp/libraries/use-c-run-time
    42  	//
    43  	// > When building a DLL which uses any of the C Run-time libraries, in order to ensure that the CRT is properly initialized, either
    44  	// > 1. the initialization function must be named DllMain() and the entry point must be specified with the linker option -entry:_DllMainCRTStartup@12 - or -
    45  	// > 2. the DLL's entry point must explicitly call CRT_INIT() on process attach and process detach
    46  	//
    47  	// This isn't really helpful from a user perspective when it comes to indicating if there is an entrypoint or not
    48  	// since it will always effectively be true for DLLs! All DLLs and Executables (aka "modules") have a single
    49  	// entrypoint, _GetPEImageBase, but we're more interested in the logical idea of an entrypoint.
    50  	// See https://learn.microsoft.com/en-us/windows/win32/psapi/module-information for more details.
    51  
    52  	var hasLibEntrypoint, hasExeEntrypoint bool
    53  	for _, s := range f.Symbols {
    54  		if windowsExeEntrypoints.Has(s.Name) {
    55  			hasExeEntrypoint = true
    56  		}
    57  		if windowsDllEntrypoints.Has(s.Name) {
    58  			hasLibEntrypoint = true
    59  		}
    60  	}
    61  
    62  	switch v := f.OptionalHeader.(type) {
    63  	case *pe.OptionalHeader32:
    64  		return v.AddressOfEntryPoint > 0 && !hasLibEntrypoint && hasExeEntrypoint
    65  	case *pe.OptionalHeader64:
    66  		return v.AddressOfEntryPoint > 0 && !hasLibEntrypoint && hasExeEntrypoint
    67  	}
    68  	return false
    69  }
    70  
    71  func peHasExports(f *pe.File) bool {
    72  	if f.OptionalHeader == nil {
    73  		return false
    74  	}
    75  
    76  	switch v := f.OptionalHeader.(type) {
    77  	case *pe.OptionalHeader32:
    78  		return v.DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_EXPORT].Size > 0
    79  	case *pe.OptionalHeader64:
    80  		return v.DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_EXPORT].Size > 0
    81  	}
    82  
    83  	return false
    84  }