From ebed875d64c62c445bd7c76551f856412cd72195 Mon Sep 17 00:00:00 2001 From: blacktop Date: Mon, 12 Sep 2022 14:30:29 -0600 Subject: [PATCH] feat: add NEW `--all-fileset-entries` flag to `ipsw macho disass` to allow for disass-ing ALL kernelcache file-set entries at once --- cmd/ipsw/cmd/macho/macho_disass.go | 275 ++++++++++++++++------------- pkg/disass/macho.go | 2 +- 2 files changed, 152 insertions(+), 125 deletions(-) diff --git a/cmd/ipsw/cmd/macho/macho_disass.go b/cmd/ipsw/cmd/macho/macho_disass.go index c5c05f49b..70e53d44e 100644 --- a/cmd/ipsw/cmd/macho/macho_disass.go +++ b/cmd/ipsw/cmd/macho/macho_disass.go @@ -33,6 +33,7 @@ import ( "github.com/blacktop/go-macho/types" "github.com/blacktop/ipsw/pkg/disass" "github.com/fatih/color" + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -48,6 +49,7 @@ func init() { machoDisassCmd.Flags().BoolP("quiet", "q", false, "Do NOT markup analysis (Faster)") // machoDisassCmd.Flags().StringP("input", "i", "", "Input function JSON file") machoDisassCmd.Flags().StringP("fileset-entry", "t", "", "Which fileset entry to analyze") + machoDisassCmd.Flags().BoolP("all-fileset-entries", "z", false, "Parse all fileset entries") machoDisassCmd.Flags().StringP("section", "x", "", "Disassemble an entire segment/section (i.e. __TEXT_EXEC.__text)") machoDisassCmd.Flags().String("cache", "", "Path to .a2s addr to sym cache file (speeds up analysis)") @@ -59,6 +61,7 @@ func init() { viper.BindPFlag("macho.disass.quiet", machoDisassCmd.Flags().Lookup("quiet")) // viper.BindPFlag("macho.disass.input", machoDisassCmd.Flags().Lookup("input")) viper.BindPFlag("macho.disass.fileset-entry", machoDisassCmd.Flags().Lookup("fileset-entry")) + viper.BindPFlag("macho.disass.all-fileset-entries", machoDisassCmd.Flags().Lookup("all-fileset-entries")) viper.BindPFlag("macho.disass.section", machoDisassCmd.Flags().Lookup("section")) viper.BindPFlag("macho.disass.cache", machoDisassCmd.Flags().Lookup("cache")) @@ -74,6 +77,7 @@ var machoDisassCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { var m *macho.File + var ms []*macho.File var middleAddr uint64 var symbolMap map[uint64]string @@ -96,7 +100,6 @@ var machoDisassCmd = &cobra.Command{ filesetEntry := viper.GetString("macho.disass.fileset-entry") cacheFile := viper.GetString("macho.disass.cache") - // funcFile, _ := cmd.Flags().GetString("input") allFuncs := false if len(symbolName) > 0 && startAddr != 0 { @@ -105,6 +108,11 @@ var machoDisassCmd = &cobra.Command{ allFuncs = true // return fmt.Errorf("you must supply a --symbol OR --vaddr to disassemble") } + if len(filesetEntry) > 0 && viper.GetBool("macho.disass.all-fileset-entries") { + return fmt.Errorf("you can only use --fileset-entry OR --all-fileset-entries (not both)") + } else if viper.GetBool("macho.disass.all-fileset-entries") && len(segmentSection) == 0 { + log.Warn("you probably want to add --section '__TEXT_EXEC.__text'; as the NEW MH_FILESET entries don't ALL have LC_FUNCTION_STARTS") + } machoPath := filepath.Clean(args[0]) @@ -126,6 +134,11 @@ var machoDisassCmd = &cobra.Command{ } } + if !strings.Contains(strings.ToLower(m.FileHeader.SubCPU.String(m.CPU)), "arm64") { + log.Errorf("can only disassemble arm64 binaries") + return nil + } + if len(filesetEntry) > 0 { if m.FileTOC.FileHeader.Type == types.MH_FILESET { m, err = m.GetFileSetFileByName(filesetEntry) @@ -135,42 +148,153 @@ var machoDisassCmd = &cobra.Command{ } else { log.Error("MachO type is not MH_FILESET (cannot use --fileset-entry)") } - } - - if !strings.Contains(strings.ToLower(m.FileHeader.SubCPU.String(m.CPU)), "arm64") { - log.Errorf("can only disassemble arm64 binaries") - return nil + ms = append(ms, m) + } else if viper.GetBool("macho.disass.all-fileset-entries") { + for _, fe := range m.FileSets() { + mfe, err := m.GetFileSetFileByName(fe.EntryID) + if err != nil { + return fmt.Errorf("failed to parse entry %s: %v", filesetEntry, err) + } + ms = append(ms, mfe) + } + } else { + ms = append(ms, m) } symbolMap = make(map[uint64]string) - if !quiet { - if len(cacheFile) == 0 { - cacheFile = machoPath + ".a2s" + for _, m := range ms { + if !quiet { + if len(cacheFile) == 0 { + cacheFile = machoPath + ".a2s" + } + if _, err := os.Stat(cacheFile); errors.Is(err, os.ErrNotExist) { + if _, err := os.Create(cacheFile); err != nil { + return fmt.Errorf("failed to create address-to-symbol cache file %s: %v", cacheFile, err) + } + } else { + log.Infof("Loading symbol cache file...") + if f, err := os.Open(cacheFile); err != nil { + return fmt.Errorf("failed to open address-to-symbol cache file %s: %v", cacheFile, err) + } else { + if err := gob.NewDecoder(f).Decode(&symbolMap); err != nil { + return fmt.Errorf("failed to decode address-to-symbol cache file: %v", err) + } + f.Close() + } + } } - if a2sFile, err := os.Create(cacheFile); err == nil { - // Decoding the serialized data - log.Infof("Loading symbol cache file...") - if err := gob.NewDecoder(a2sFile).Decode(&symbolMap); err != nil { - return fmt.Errorf("failed to decode cache file: %v", err) + + if allFuncs && len(segmentSection) == 0 { + for _, fn := range m.GetFunctions() { + data, err := m.GetFunctionData(fn) + if err != nil { + log.Errorf("failed to get data for function: %v", err) + continue + } + + engine := disass.NewMachoDisass(m, &symbolMap, &disass.Config{ + Data: data, + StartAddress: fn.StartAddr, + Middle: 0, + AsJSON: asJSON, + Demangle: demangleFlag, + Quite: quiet, + Color: viper.GetBool("color"), + }) + + //*********************** + //* First pass ANALYSIS * + //*********************** + if !quiet { + if err := engine.Triage(); err != nil { + return fmt.Errorf("first pass triage failed: %v", err) + } + if len(symbolMap) == 0 { + if err := engine.Analyze(); err != nil { + return fmt.Errorf("MachO analysis failed: %v", err) + } + } + if err := engine.SaveAddrToSymMap(cacheFile); err != nil { + log.Errorf("failed to save symbol map: %v", err) + } + } + //*************** + //* DISASSEMBLE * + //*************** + disass.Disassemble(engine) } } else { - log.Errorf("failed to open cache file %s: %v", cacheFile, err) - } - } + if len(symbolName) > 0 { + startAddr, err = m.FindSymbolAddress(symbolName) + if err != nil { + return err + } + } else if len(segmentSection) > 0 { + parts := strings.Split(segmentSection, ".") + if len(parts) != 2 { + return fmt.Errorf("invalid --section format, must be segment.section") + } + if sec := m.Section(parts[0], parts[1]); sec != nil { + startAddr = sec.Addr + instructions = sec.Size / 4 + } else { + return fmt.Errorf("failed to find section %s", segmentSection) + } + } + // startAddr > 0 TODO: support slides + // if slide > 0 { + // startAddr = startAddr - slide + // } - if allFuncs && len(segmentSection) == 0 { - for _, fn := range m.GetFunctions() { - data, err := m.GetFunctionData(fn) - if err != nil { - log.Errorf("failed to get data for function: %v", err) - continue + /* + * Read in data to disassemble + */ + var data []byte + if instructions > 0 { + off, err := m.GetOffset(startAddr) + if err != nil { + return err + } + data = make([]byte, instructions*4) + if _, err := m.ReadAt(data, int64(off)); err != nil { + return err + } + } else { + if fn, err := m.GetFunctionForVMAddr(startAddr); err == nil { + soff, err := m.GetOffset(fn.StartAddr) + if err != nil { + return err + } + data = make([]byte, uint64(fn.EndAddr-fn.StartAddr)) + if _, err := m.ReadAt(data, int64(soff)); err != nil { + return err + } + if startAddr != fn.StartAddr { + middleAddr = startAddr + startAddr = fn.StartAddr + } + } else { + log.Warnf("disassembling 100 instructions at %#x", startAddr) + instructions = 100 + off, err := m.GetOffset(startAddr) + if err != nil { + return err + } + data = make([]byte, instructions*4) + if _, err := m.ReadAt(data, int64(off)); err != nil { + return err + } + } + } + if len(data) == 0 { + log.Fatal("failed to disassemble") } engine := disass.NewMachoDisass(m, &symbolMap, &disass.Config{ Data: data, - StartAddress: fn.StartAddr, - Middle: 0, + StartAddress: startAddr, + Middle: middleAddr, AsJSON: asJSON, Demangle: demangleFlag, Quite: quiet, @@ -184,10 +308,8 @@ var machoDisassCmd = &cobra.Command{ if err := engine.Triage(); err != nil { return fmt.Errorf("first pass triage failed: %v", err) } - if len(symbolMap) == 0 { - if err := engine.Analyze(); err != nil { - return fmt.Errorf("MachO analysis failed: %v", err) - } + if err := engine.Analyze(); err != nil { + return fmt.Errorf("MachO analysis failed: %v", err) } if err := engine.SaveAddrToSymMap(cacheFile); err != nil { log.Errorf("failed to save symbol map: %v", err) @@ -198,101 +320,6 @@ var machoDisassCmd = &cobra.Command{ //*************** disass.Disassemble(engine) } - } else { - if len(symbolName) > 0 { - startAddr, err = m.FindSymbolAddress(symbolName) - if err != nil { - return err - } - } else if len(segmentSection) > 0 { - parts := strings.Split(segmentSection, ".") - if len(parts) != 2 { - return fmt.Errorf("invalid --section format, must be segment.section") - } - if sec := m.Section(parts[0], parts[1]); sec != nil { - startAddr = sec.Addr - instructions = sec.Size / 4 - } else { - return fmt.Errorf("failed to find section %s", segmentSection) - } - } - // startAddr > 0 TODO: support slides - // if slide > 0 { - // startAddr = startAddr - slide - // } - - /* - * Read in data to disassemble - */ - var data []byte - if instructions > 0 { - off, err := m.GetOffset(startAddr) - if err != nil { - return err - } - data = make([]byte, instructions*4) - if _, err := m.ReadAt(data, int64(off)); err != nil { - return err - } - } else { - if fn, err := m.GetFunctionForVMAddr(startAddr); err == nil { - soff, err := m.GetOffset(fn.StartAddr) - if err != nil { - return err - } - data = make([]byte, uint64(fn.EndAddr-fn.StartAddr)) - if _, err := m.ReadAt(data, int64(soff)); err != nil { - return err - } - if startAddr != fn.StartAddr { - middleAddr = startAddr - startAddr = fn.StartAddr - } - } else { - log.Warnf("disassembling 100 instructions at %#x", startAddr) - instructions = 100 - off, err := m.GetOffset(startAddr) - if err != nil { - return err - } - data = make([]byte, instructions*4) - if _, err := m.ReadAt(data, int64(off)); err != nil { - return err - } - } - } - if len(data) == 0 { - log.Fatal("failed to disassemble") - } - - engine := disass.NewMachoDisass(m, &symbolMap, &disass.Config{ - Data: data, - StartAddress: startAddr, - Middle: middleAddr, - AsJSON: asJSON, - Demangle: demangleFlag, - Quite: quiet, - Color: viper.GetBool("color"), - }) - - //*********************** - //* First pass ANALYSIS * - //*********************** - if !quiet { - if err := engine.Triage(); err != nil { - return fmt.Errorf("first pass triage failed: %v", err) - } - if err := engine.Analyze(); err != nil { - return fmt.Errorf("MachO analysis failed: %v", err) - } - if err := engine.SaveAddrToSymMap(cacheFile); err != nil { - log.Errorf("failed to save symbol map: %v", err) - } - } - //*************** - //* DISASSEMBLE * - //*************** - disass.Disassemble(engine) } return nil diff --git a/pkg/disass/macho.go b/pkg/disass/macho.go index 8ed954917..eeccff59b 100644 --- a/pkg/disass/macho.go +++ b/pkg/disass/macho.go @@ -456,7 +456,7 @@ func (d *MachoDisass) SaveAddrToSymMap(dest string) error { utils.Indent(log.Warn, 2)("creating in the temp folder") utils.Indent(log.Warn, 3)(fmt.Sprintf("to use in the future you must supply the flag: --cache %s ", tempa2sfile)) } else if err != nil { - return err + return fmt.Errorf("failed to open address to symbol cache file %s: %v", dest, err) } defer of.Close()