From fa5fd075df7afaea37b4283739dfce5bc02173a0 Mon Sep 17 00:00:00 2001 From: NSEcho Date: Sun, 17 Sep 2023 20:19:39 +0200 Subject: [PATCH] feat: add flag to ignore crashes --- cmd/fuzz.go | 18 +++++++++++++++--- mutator/helpers.go | 30 ++++++++++++++++++++++++++++++ mutator/mutator.go | 39 +++++++++++++++++++++++++++++++++++---- 3 files changed, 80 insertions(+), 7 deletions(-) diff --git a/cmd/fuzz.go b/cmd/fuzz.go index b9c2307..1f267ce 100644 --- a/cmd/fuzz.go +++ b/cmd/fuzz.go @@ -1,7 +1,9 @@ package cmd import ( + "context" "errors" + "fmt" "github.com/fatih/color" "github.com/frida/frida-go/frida" "github.com/nsecho/furlzz/mutator" @@ -77,6 +79,11 @@ var fuzzCmd = &cobra.Command{ return err } + crash, err := cmd.Flags().GetBool("crash") + if err != nil { + return err + } + l.Infof("Fuzzing base URL \"%s\"", base) if strings.Contains(base, "FUZZ") { l.Infof("Read %d inputs from %s directory", @@ -123,7 +130,7 @@ var fuzzCmd = &cobra.Command{ l.Infof("Session detached; reason=%s", reason.String()) out := crashSHA256(lastInput) err := func() error { - f, err := os.Create(out) + f, err := os.Create(fmt.Sprintf("fcrash_%s_%s", app, out)) if err != nil { return err } @@ -167,13 +174,17 @@ var fuzzCmd = &cobra.Command{ _ = script.ExportsCall("setup", method, uiapp, delegate, scene) l.Infof("Finished setup") - m := mutator.NewMutator(base, runs, fn, validInputs...) + m := mutator.NewMutator(base, app, runs, fn, crash, validInputs...) ch := m.Mutate() for mutated := range ch { lastInput = mutated.Input l.Infof("[%s] %s\n", color.New(color.FgCyan).Sprintf("%s", mutated.Mutation), mutated.Input) - _ = script.ExportsCall("fuzz", method, mutated.Input) + ctx, _ := context.WithTimeout(context.Background(), 5*time.Second) + if err := script.ExportsCallWithContext(ctx, "fuzz", method, mutated.Input); err == frida.ErrContextCancelled { + sess.Detach() + break + } if timeout > 0 { time.Sleep(time.Duration(timeout) * time.Second) } @@ -191,6 +202,7 @@ func init() { fuzzCmd.Flags().StringP("delegate", "d", "", "if the method is scene_activity, you need to specify UISceneDelegate class") fuzzCmd.Flags().StringP("uiapp", "u", "", "UIApplication name") fuzzCmd.Flags().StringP("scene", "s", "", "scene class name") + fuzzCmd.Flags().BoolP("crash", "c", false, "ignore previous crashes") fuzzCmd.Flags().UintP("runs", "r", 0, "number of runs") fuzzCmd.Flags().UintP("timeout", "t", 1, "sleep X seconds between each case") diff --git a/mutator/helpers.go b/mutator/helpers.go index b793089..be013d8 100644 --- a/mutator/helpers.go +++ b/mutator/helpers.go @@ -1,5 +1,11 @@ package mutator +import ( + "io" + "os" + "path/filepath" +) + func (m *Mutator) getFuzzedInput() string { if m.multipleRounds { if m.lastInput == "" { @@ -17,3 +23,27 @@ func (m *Mutator) fetchInput() string { k := m.r.Intn(len(m.validInputs)) return m.validInputs[k] } + +func readCrashes(app string) ([]string, error) { + files, _ := filepath.Glob("fcrash_*_*") + + var crashes []string + for _, fl := range files { + data, err := func() ([]byte, error) { + f, err := os.Open(fl) + if err != nil { + return nil, err + } + defer f.Close() + + data, _ := io.ReadAll(f) + return data, nil + }() + if err != nil { + return nil, err + } + crashes = append(crashes, string(data)) + } + + return crashes, nil +} diff --git a/mutator/mutator.go b/mutator/mutator.go index 44c02cd..dd80ab7 100644 --- a/mutator/mutator.go +++ b/mutator/mutator.go @@ -1,21 +1,34 @@ package mutator import ( + "bytes" + "fmt" "math/rand" "strings" "time" ) -func NewMutator(inp string, runs uint, fnName string, validInputs ...string) *Mutator { +func NewMutator(inp, app string, runs uint, fnName string, ignoreCrashes bool, validInputs ...string) *Mutator { + var crashes []string + if ignoreCrashes { + c, err := readCrashes(app) + if err != nil { + panic(fmt.Sprintf("error reading crashes: %v", err)) + } + crashes = c + } + return &Mutator{ fuzzIdx: strings.Index(inp, "FUZZ"), baseURL: inp, input: inp, fnName: fnName, + ignoreCrashes: ignoreCrashes, ch: make(chan *Mutated, 100), r: rand.New(rand.NewSource(time.Now().UnixNano())), runs: runs, validInputs: validInputs, + crashes: crashes, multipleRounds: false, } } @@ -27,9 +40,11 @@ type Mutator struct { input string lastInput string fnName string + ignoreCrashes bool ch chan *Mutated r *rand.Rand validInputs []string + crashes []string multipleRounds bool } @@ -42,19 +57,25 @@ func (m *Mutator) Mutate() <-chan *Mutated { go func() { if m.runs > 0 { for i := 0; i < int(m.runs); i++ { - m.mutateAndSend() + inp := m.mutateAndSend() + for !inp { + inp = m.mutateAndSend() + } } close(m.ch) } else { for { - m.mutateAndSend() + inp := m.mutateAndSend() + for !inp { + inp = m.mutateAndSend() + } } } }() return m.ch } -func (m *Mutator) mutateAndSend() { +func (m *Mutator) mutateAndSend() bool { var mutatedInput string var method string mut := m.r.Intn(len(mutations) + 1) @@ -92,6 +113,15 @@ func (m *Mutator) mutateAndSend() { mutatedInput = applyFunctions[m.fnName](mutatedInput) } + if m.ignoreCrashes && len(m.crashes) > 0 { + for _, crash := range m.crashes { + crashInp := strings.Replace(crash, m.baseURL[:m.fuzzIdx], "", -1) + if bytes.Equal([]byte(crashInp), []byte(mutatedInput)) { + return false + } + } + } + if m.fuzzIdx == -1 || len(m.validInputs) == 0 { m.ch <- &Mutated{ Input: mutatedInput, @@ -104,4 +134,5 @@ func (m *Mutator) mutateAndSend() { } } m.lastInput = mutatedInput + return true }