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 }