Skip to content

Commit

Permalink
feat: add ability to use --pattern for regex (SLOW) search OR multi s…
Browse files Browse the repository at this point in the history
…tring (FAST) search via pos args/stdin
  • Loading branch information
blacktop committed Jul 16, 2024
1 parent 175a2f8 commit d00d0bf
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 44 deletions.
72 changes: 62 additions & 10 deletions cmd/ipsw/cmd/dyld/dyld_str.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ THE SOFTWARE.
package dyld

import (
"bufio"
"fmt"
"io"
"os"
"path/filepath"
"strings"
Expand All @@ -38,15 +40,23 @@ import (

func init() {
DyldCmd.AddCommand(StrSearchCmd)
StrSearchCmd.Flags().StringP("pattern", "p", "", "Regex match strings")
StrSearchCmd.Flags().StringP("pattern", "p", "", "Regex match strings (SLOW)")
viper.BindPFlag("dyld.str.pattern", StrSearchCmd.Flags().Lookup("pattern"))
}

// StrSearchCmd represents the str command
var StrSearchCmd = &cobra.Command{
Use: "str <DSC>",
Use: "str <DSC> [STRING...]",
Short: "Search dyld_shared_cache for string",
Args: cobra.ExactArgs(1),
Example: ` # Perform FAST byte search for string in dyld_shared_cache
❯ ipsw dsc str DSC "string1"
# Perform FAST byte search for multiple strings in dyld_shared_cache
❯ ipsw dsc str DSC "string1" "string2
# Perform FAST byte search for strings from stdin in dyld_shared_cache
❯ cat strings.txt | ipsw dsc str DSC
# Perform SLOW regex search for string in dyld_shared_cache
❯ ipsw dsc str DSC --pattern "REGEX_PATTERN"`,
Args: cobra.MinimumNArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return getDSCs(toComplete), cobra.ShellCompDirectiveDefault
},
Expand All @@ -58,15 +68,17 @@ var StrSearchCmd = &cobra.Command{
}
color.NoColor = viper.GetBool("no-color")

// flags
pattern := viper.GetString("dyld.str.pattern")

// validate flags
if pattern != "" && len(args) > 1 {
return fmt.Errorf("cannot use --pattern with positional STRING arguments")
}
dscPath := filepath.Clean(args[0])

fileInfo, err := os.Lstat(dscPath)
if err != nil {
return fmt.Errorf("file %s does not exist", dscPath)
}

// Check if file is a symlink
if fileInfo.Mode()&os.ModeSymlink != 0 {
symlinkPath, err := os.Readlink(dscPath)
Expand All @@ -86,10 +98,50 @@ var StrSearchCmd = &cobra.Command{
}
defer f.Close()

log.Info("Searching for strings...")
strs, err := dscCmd.GetStrings(f, pattern)
if err != nil {
return err
var strs []dscCmd.String

if pattern != "" {
log.Info("Searching for strings via REGEX pattern...")
strs, err = dscCmd.GetStringsRegex(f, pattern)
if err != nil {
return err
}
} else {
log.Info("Searching for strings...")
var searchStrings []string

if len(args) > 1 {
// Read from positional args
searchStrings = args[1:]
} else {
// Read from stdin
stat, err := os.Stdin.Stat()
if err != nil {
return fmt.Errorf("failed to read from stdin: %v", err)
}
if (stat.Mode() & os.ModeCharDevice) == 0 {
reader := bufio.NewReader(os.Stdin)
var inputBuilder strings.Builder
for {
part, err := reader.ReadString('\n')
if err == io.EOF {
break
}
if err != nil {
return fmt.Errorf("failed to read from stdin: %v", err)
}
inputBuilder.WriteString(strings.TrimSuffix(part, "\n"))
}
searchStrings = strings.Split(inputBuilder.String(), " ")
} else {
return fmt.Errorf("no input provided via stdin")
}
}

strs, err = dscCmd.GetStrings(f, searchStrings...)
if err != nil {
return err
}
}

var out strings.Builder
Expand Down
75 changes: 41 additions & 34 deletions internal/commands/dsc/dsc.go
Original file line number Diff line number Diff line change
Expand Up @@ -592,52 +592,59 @@ func GetSymbols(f *dyld.File, lookups []Symbol) ([]Symbol, error) {
}

// GetStrings returns a list of strings from a dyld_shared_cache file for a given regex pattern
func GetStrings(f *dyld.File, pattern string) ([]String, error) {
func GetStrings(f *dyld.File, in ...string) ([]String, error) {
var strs []String

if len(pattern) == 0 {
return nil, fmt.Errorf("pattern cannot be empty")
}

strRE, err := regexp.Compile(pattern)
if err != nil {
return nil, fmt.Errorf("invalid regex: %w", err)
if len(in) == 0 {
return nil, fmt.Errorf("search strings cannot be empty")
}

matches, err := f.Search([]byte(pattern))
if err != nil {
return nil, fmt.Errorf("failed to search for pattern: %v", err)
}
for uuid, ms := range matches {
for _, match := range ms {
s := String{Offset: match}
if mapping, err := f.GetMappingForOffsetForUUID(uuid, match); err == nil {
s.Mapping = mapping.Name
if sc := f.GetSubCacheInfo(uuid); sc != nil {
s.Mapping += fmt.Sprintf(", sub_cache (%s)", sc.Extention)
for _, search := range in {
matches, err := f.Search([]byte(search))
if err != nil {
return nil, fmt.Errorf("failed to search for pattern: %v", err)
}
for uuid, ms := range matches {
for _, match := range ms {
s := String{Offset: match}
if mapping, err := f.GetMappingForOffsetForUUID(uuid, match); err == nil {
s.Mapping = mapping.Name
if sc := f.GetSubCacheInfo(uuid); sc != nil {
s.Mapping += fmt.Sprintf(", sub_cache (%s)", sc.Extention)
}
} else {
if sc := f.GetSubCacheInfo(uuid); sc != nil {
s.Mapping += fmt.Sprintf("sub_cache (%s)", sc.Extention)
}
}
} else {
if sc := f.GetSubCacheInfo(uuid); sc != nil {
s.Mapping += fmt.Sprintf("sub_cache (%s)", sc.Extention)
if str, err := f.GetCStringAtOffsetForUUID(uuid, match); err == nil {
s.String = strings.TrimSuffix(strings.TrimSpace(str), "\n")
}
}
if str, err := f.GetCStringAtOffsetForUUID(uuid, match); err == nil {
s.String = strings.TrimSuffix(strings.TrimSpace(str), "\n")
}
if addr, err := f.GetVMAddressForUUID(uuid, match); err == nil {
s.Address = addr
if image, err := f.GetImageContainingVMAddr(addr); err == nil {
s.Image = filepath.Base(image.Name)
if addr, err := f.GetVMAddressForUUID(uuid, match); err == nil {
s.Address = addr
if image, err := f.GetImageContainingVMAddr(addr); err == nil {
s.Image = filepath.Base(image.Name)
}
}
strs = append(strs, s)
}
strs = append(strs, s)
}
}
if len(matches) > 0 {
return strs, nil

return strs, nil
}

func GetStringsRegex(f *dyld.File, pattern string) ([]String, error) {
var strs []String

if len(pattern) == 0 {
return nil, fmt.Errorf("'pattern' cannot be empty")
}

log.Warn("No strings found in dyld_shared_cache using FAST byte search, falling back to slow MachO parsing/regex search...")
strRE, err := regexp.Compile(pattern)
if err != nil {
return nil, fmt.Errorf("invalid regex: %w", err)
}

for _, i := range f.Images {
m, err := i.GetMacho()
Expand Down

0 comments on commit d00d0bf

Please sign in to comment.