github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/file/cataloger/executable/macho.go (about) 1 package executable 2 3 import ( 4 "debug/macho" 5 6 "github.com/anchore/syft/syft/file" 7 "github.com/anchore/syft/syft/internal/unionreader" 8 ) 9 10 // source http://www.cilinder.be/docs/next/NeXTStep/3.3/nd/DevTools/14_MachO/MachO.htmld/index.html 11 const ( 12 machoNPExt uint8 = 0x10 /* N_PEXT: private external symbol bit */ 13 machoNExt uint8 = 0x01 /* N_EXT: external symbol bit, set for external symbols */ 14 // > #define LC_REQ_DYLD 0x80000000 15 // > #define LC_MAIN (0x28|LC_REQ_DYLD) /* replacement for LC_UNIXTHREAD */ 16 lcMain = 0x28 | 0x80000000 17 ) 18 19 func findMachoFeatures(data *file.Executable, reader unionreader.UnionReader) error { 20 // TODO: support security features 21 22 // TODO: support multi-architecture binaries 23 f, err := macho.NewFile(reader) 24 if err != nil { 25 return err 26 } 27 28 libs, err := f.ImportedLibraries() 29 if err != nil { 30 return err 31 } 32 33 data.ImportedLibraries = libs 34 data.HasEntrypoint = machoHasEntrypoint(f) 35 data.HasExports = machoHasExports(f) 36 37 return nil 38 } 39 40 func machoHasEntrypoint(f *macho.File) bool { 41 // derived from struct entry_point_command found from which explicitly calls out LC_MAIN: 42 // https://opensource.apple.com/source/xnu/xnu-2050.18.24/EXTERNAL_HEADERS/mach-o/loader.h 43 // we need to look for both LC_MAIN and LC_UNIXTHREAD commands to determine if the file is an executable 44 // 45 // this is akin to: 46 // otool -l ./path/to/bin | grep -A4 LC_MAIN 47 // otool -l ./path/to/bin | grep -A4 LC_UNIXTHREAD 48 for _, l := range f.Loads { 49 data := l.Raw() 50 cmd := f.ByteOrder.Uint32(data) 51 52 if macho.LoadCmd(cmd) == macho.LoadCmdUnixThread || macho.LoadCmd(cmd) == lcMain { 53 return true 54 } 55 } 56 return false 57 } 58 59 func machoHasExports(f *macho.File) bool { 60 if f == nil || f.Symtab == nil { 61 return false 62 } 63 for _, sym := range f.Symtab.Syms { 64 // look for symbols that are: 65 // - not private and are external 66 // - do not have an N_TYPE value of N_UNDF (undefined symbol) 67 // 68 // here's the bit layout for the n_type field: 69 // 0000 0000 70 // ─┬─│ ─┬─│ 71 // │ │ │ └─ N_EXT (external symbol) 72 // │ │ └─ N_TYPE (N_UNDF, N_ABS, N_SECT, N_PBUD, N_INDR) 73 // │ └─ N_PEXT (private external symbol) 74 // └─ N_STAB (debugging symbol) 75 // 76 isExternal := sym.Type&machoNExt == machoNExt 77 isPrivate := sym.Type&machoNPExt == machoNPExt 78 nTypeIsUndefined := sym.Type&0x0e == 0 79 80 if isExternal && !isPrivate { 81 if sym.Name == "_main" || sym.Name == "__mh_execute_header" { 82 // ...however there are some symbols that are not exported but are still important 83 // for debugging or as an entrypoint, so we need to explicitly check for them 84 continue 85 } 86 if nTypeIsUndefined { 87 continue 88 } 89 // we have a symbol that is not private and is external 90 // and is not undefined, so it is an export 91 return true 92 } 93 } 94 return false 95 }