From 5397bc771aec3906969e78874055fd8c65e344e5 Mon Sep 17 00:00:00 2001 From: NSEcho Date: Wed, 13 Sep 2023 19:28:45 +0200 Subject: [PATCH] feat: add subcommands --- cmd/crash.go | 87 +++++++++++++++++++++ cmd/fuzz.go | 177 ++++++++++++++++++++++++++++++++++++++++++ cmd/root.go | 66 ++++++++++++++++ cmd/session.go | 44 +++++++++++ go.mod | 1 + go.sum | 1 + main.go | 203 +------------------------------------------------ 7 files changed, 378 insertions(+), 201 deletions(-) create mode 100644 cmd/crash.go create mode 100644 cmd/fuzz.go create mode 100644 cmd/root.go create mode 100644 cmd/session.go diff --git a/cmd/crash.go b/cmd/crash.go new file mode 100644 index 0000000..dcd6268 --- /dev/null +++ b/cmd/crash.go @@ -0,0 +1,87 @@ +package cmd + +import ( + "errors" + "github.com/frida/frida-go/frida" + "github.com/spf13/cobra" + "io" + "os" + "time" +) + +var crashCmd = &cobra.Command{ + Use: "crash", + Short: "Run the application with crash", + RunE: func(cmd *cobra.Command, args []string) error { + sfile, err := cmd.Flags().GetString("session") + if err != nil { + return err + } + + l.Infof("Reading session file %s", sfile) + s, err := NewSession(sfile) + if err != nil { + return err + } + + crash, err := cmd.Flags().GetString("crash") + if err != nil { + return err + } + + inpf, err := os.Open(crash) + if err != nil { + return err + } + defer inpf.Close() + + input, _ := io.ReadAll(inpf) + + l.Infof("Read %s from crash file", string(input)) + + dev := frida.USBDevice() + if dev == nil { + return errors.New("no USB device detected") + } + defer dev.Clean() + + sess, err := dev.Attach(s.App, nil) + if err != nil { + return err + } + defer sess.Clean() + + l.Infof("Attached to %s", s.App) + + script, err := sess.CreateScript(scriptContent) + if err != nil { + return err + } + + script.On("message", func(message string) { + l.Infof("script output: %s", message) + }) + + if err := script.Load(); err != nil { + return err + } + defer script.Clean() + + l.Infof("Loaded script") + + l.Infof("Sleeping for two seconds and triggering crash") + + time.Sleep(2 * time.Second) + + _ = script.ExportsCall("fuzz", s.Method, string(input)) + + return nil + }, +} + +func init() { + crashCmd.Flags().StringP("session", "s", "", "session path") + crashCmd.Flags().StringP("crash", "c", "", "crash file") + + rootCmd.AddCommand(crashCmd) +} diff --git a/cmd/fuzz.go b/cmd/fuzz.go new file mode 100644 index 0000000..6a763df --- /dev/null +++ b/cmd/fuzz.go @@ -0,0 +1,177 @@ +package cmd + +import ( + "errors" + "github.com/fatih/color" + "github.com/frida/frida-go/frida" + "github.com/nsecho/furlzz/mutator" + "github.com/spf13/cobra" + "os" + "strings" + "time" +) + +var fuzzCmd = &cobra.Command{ + Use: "fuzz", + Short: "Fuzz URL scheme", + RunE: func(cmd *cobra.Command, args []string) error { + var validInputs []string + var err error + + base, err := cmd.Flags().GetString("base") + if err != nil { + return err + } + if base == "" { + return errors.New("base URL cannot be empty") + } + + input, err := cmd.Flags().GetString("input") + if err != nil { + return err + } + + if input == "" && strings.Contains(base, "FUZZ") { + return errors.New("input directory cannot be empty when using FUZZ keyword") + } + + if strings.Contains(base, "FUZZ") { + validInputs, err = readInputs(input) + if err != nil { + return err + } + } + + runs, err := cmd.Flags().GetUint("runs") + if err != nil { + return err + } + + fn, err := cmd.Flags().GetString("function") + if err != nil { + return err + } + + timeout, err := cmd.Flags().GetUint("timeout") + if err != nil { + return err + } + + method, err := cmd.Flags().GetString("method") + 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", + len(validInputs), input) + } else { + l.Infof("Fuzzing base URL") + } + + if runs == 0 { + l.Infof("Fuzzing indefinitely") + } else { + l.Infof("Fuzzing with %d mutated inputs", runs) + } + + if timeout != 0 { + l.Infof("Sleeping %d seconds between each fuzz case", timeout) + } + + app, err := cmd.Flags().GetString("app") + if err != nil { + return err + } + + if app == "" { + return errors.New("error: app cannot be empty") + } + + dev := frida.USBDevice() + if dev == nil { + return errors.New("no USB device detected") + } + defer dev.Clean() + + sess, err := dev.Attach(app, nil) + if err != nil { + return err + } + + l.Infof("Attached to %s", app) + + var lastInput string + + sess.On("detached", func(reason frida.SessionDetachReason, crash *frida.Crash) { + l.Infof("Session detached; reason=%s", reason.String()) + out := crashSHA256(lastInput) + err := func() error { + f, err := os.Create(out) + if err != nil { + return err + } + f.WriteString(lastInput) + return nil + }() + if err != nil { + l.Errorf("Error writing crash file: %v", err) + } else { + l.Infof("Written crash to: %s", out) + } + s := Session{ + App: app, + Base: base, + Function: fn, + Method: method, + } + if err := s.WriteToFile(); err != nil { + l.Errorf("Error writing session file: %v", err) + } else { + l.Infof("Written session file") + } + os.Exit(1) + }) + + script, err := sess.CreateScript(scriptContent) + if err != nil { + return err + } + + script.On("message", func(message string) { + l.Infof("script output: %s", message) + }) + + if err := script.Load(); err != nil { + return err + } + + l.Infof("Loaded script") + + m := mutator.NewMutator(base, runs, fn, 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) + if timeout > 0 { + time.Sleep(time.Duration(timeout) * time.Second) + } + } + return nil + }, +} + +func init() { + fuzzCmd.Flags().StringP("app", "a", "Gadget", "Application name to attach to") + fuzzCmd.Flags().StringP("base", "b", "", "base URL to fuzz") + fuzzCmd.Flags().StringP("input", "i", "", "path to input directory") + fuzzCmd.Flags().StringP("function", "f", "", "apply the function to mutated input (url, base64)") + fuzzCmd.Flags().StringP("method", "m", "delegate", "method of opening url (delegate, app)") + fuzzCmd.Flags().UintP("runs", "r", 0, "number of runs") + fuzzCmd.Flags().UintP("timeout", "t", 1, "sleep X seconds between each case") + + rootCmd.AddCommand(fuzzCmd) +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..27debc6 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,66 @@ +package cmd + +import ( + "crypto/sha256" + "fmt" + "github.com/nsecho/furlzz/logger" + "github.com/spf13/cobra" + "io" + "os" + "path/filepath" +) + +var scriptContent string + +var l = *logger.NewLogger() + +var rootCmd = &cobra.Command{ + Use: "furlzz", + Short: "Fuzz iOS URL schemes", + CompletionOptions: cobra.CompletionOptions{ + DisableDefaultCmd: true, + }, + SilenceErrors: true, + SilenceUsage: true, +} + +func Execute(sc string) error { + scriptContent = sc + return rootCmd.Execute() +} + +func readInputs(dirPath string) ([]string, error) { + files, err := os.ReadDir(dirPath) + if err != nil { + return nil, err + } + + var validInputs []string + + for _, fl := range files { + if fl.IsDir() { + continue + } + data, err := func() ([]byte, error) { + f, err := os.Open(filepath.Join(dirPath, fl.Name())) + if err != nil { + return nil, err + } + defer f.Close() + + data, _ := io.ReadAll(f) + return data, nil + }() + if err != nil { + return nil, err + } + validInputs = append(validInputs, string(data)) + } + return validInputs, nil +} + +func crashSHA256(inp string) string { + h := sha256.New() + h.Write([]byte(inp)) + return fmt.Sprintf("%x", h.Sum(nil)) +} diff --git a/cmd/session.go b/cmd/session.go new file mode 100644 index 0000000..5df7a28 --- /dev/null +++ b/cmd/session.go @@ -0,0 +1,44 @@ +package cmd + +import ( + "fmt" + "gopkg.in/yaml.v3" + "os" + "time" +) + +func NewSession(sessionFile string) (*Session, error) { + var s Session + + f, err := os.Open(sessionFile) + if err != nil { + return nil, err + } + defer f.Close() + + if err := yaml.NewDecoder(f).Decode(&s); err != nil { + return nil, err + } + + return &s, nil +} + +type Session struct { + App string `yaml:"app"` + Base string `yaml:"base"` + Function string `yaml:"fn"` + Method string `yaml:"method"` +} + +func (s *Session) WriteToFile() error { + t := time.Now() + outputFilename := fmt.Sprintf("session_%s", t.Format("2006_01_02_15:04:05")) + + f, err := os.Create(outputFilename) + if err != nil { + return err + } + defer f.Close() + + return yaml.NewEncoder(f).Encode(&s) +} diff --git a/go.mod b/go.mod index d32c378..0fb89ec 100644 --- a/go.mod +++ b/go.mod @@ -15,4 +15,5 @@ require ( github.com/mattn/go-isatty v0.0.17 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/sys v0.6.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 3181752..2a75f6b 100644 --- a/go.sum +++ b/go.sum @@ -21,4 +21,5 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index c22e6c4..147e007 100644 --- a/main.go +++ b/main.go @@ -1,217 +1,18 @@ package main import ( - "crypto/sha256" _ "embed" - "errors" "fmt" - "github.com/fatih/color" - "github.com/frida/frida-go/frida" - "github.com/nsecho/furlzz/logger" - "github.com/nsecho/furlzz/mutator" - "github.com/spf13/cobra" - "io" + "github.com/nsecho/furlzz/cmd" "os" - "path/filepath" - "strings" - "time" ) //go:embed script/script.js var scriptContent string -var rootCmd = &cobra.Command{ - Use: "furlzz", - Short: "Fuzz iOS URL schemes", - RunE: func(cmd *cobra.Command, args []string) error { - l := logger.NewLogger() - - var validInputs []string - var err error - - base, err := cmd.Flags().GetString("base") - if err != nil { - return err - } - if base == "" { - return errors.New("base URL cannot be empty") - } - - input, err := cmd.Flags().GetString("input") - if err != nil { - return err - } - - if input == "" && strings.Contains(base, "FUZZ") { - return errors.New("input directory cannot be empty when using FUZZ keyword") - } - - if strings.Contains(base, "FUZZ") { - validInputs, err = readInputs(input) - if err != nil { - return err - } - } - - runs, err := cmd.Flags().GetUint("runs") - if err != nil { - return err - } - - fn, err := cmd.Flags().GetString("function") - if err != nil { - return err - } - - timeout, err := cmd.Flags().GetUint("timeout") - 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", - len(validInputs), input) - } else { - l.Infof("Fuzzing base URL") - } - - if runs == 0 { - l.Infof("Fuzzing indefinitely") - } else { - l.Infof("Fuzzing with %d mutated inputs", runs) - } - - if timeout != 0 { - l.Infof("Sleeping %d seconds between each fuzz case", timeout) - } - - app, err := cmd.Flags().GetString("app") - if err != nil { - return err - } - - if app == "" { - return errors.New("error: app cannot be empty") - } - - dev := frida.USBDevice() - if dev == nil { - return errors.New("no USB device detected") - } - defer dev.Clean() - - sess, err := dev.Attach(app, nil) - if err != nil { - return err - } - - var lastInput string - - sess.On("detached", func(reason frida.SessionDetachReason, crash *frida.Crash) { - l.Infof("session detached; reason=%s", reason.String()) - out := crashSHA256(lastInput) - err := func() error { - f, err := os.Create(out) - if err != nil { - return err - } - f.WriteString(lastInput) - return nil - }() - if err != nil { - l.Errorf("error writing crash file: %v", err) - } else { - l.Infof("written crash to: %s", out) - } - os.Exit(1) - }) - - script, err := sess.CreateScript(scriptContent) - if err != nil { - return err - } - - script.On("message", func(message string) { - l.Infof("script output: %s", message) - }) - - if err := script.Load(); err != nil { - return err - } - - m := mutator.NewMutator(base, runs, fn, validInputs...) - ch := m.Mutate() - - method, err := cmd.Flags().GetString("method") - if err != nil { - return err - } - - 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) - if timeout > 0 { - time.Sleep(time.Duration(timeout) * time.Second) - } - } - return nil - }, - CompletionOptions: cobra.CompletionOptions{ - DisableDefaultCmd: true, - }, - SilenceErrors: true, - SilenceUsage: true, -} - func main() { - rootCmd.Flags().StringP("app", "a", "Gadget", "Application name to attach to") - rootCmd.Flags().StringP("base", "b", "", "base URL to fuzz") - rootCmd.Flags().StringP("input", "i", "", "path to input directory") - rootCmd.Flags().StringP("function", "f", "", "apply the function to mutated input (url, base64)") - rootCmd.Flags().StringP("method", "m", "delegate", "method of opening url (delegate, app)") - rootCmd.Flags().UintP("runs", "r", 0, "number of runs") - rootCmd.Flags().UintP("timeout", "t", 1, "sleep X seconds between each case") - - if err := rootCmd.Execute(); err != nil { + if err := cmd.Execute(scriptContent); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } } - -func readInputs(dirPath string) ([]string, error) { - files, err := os.ReadDir(dirPath) - if err != nil { - return nil, err - } - - var validInputs []string - - for _, fl := range files { - if fl.IsDir() { - continue - } - data, err := func() ([]byte, error) { - f, err := os.Open(filepath.Join(dirPath, fl.Name())) - if err != nil { - return nil, err - } - defer f.Close() - - data, _ := io.ReadAll(f) - return data, nil - }() - if err != nil { - return nil, err - } - validInputs = append(validInputs, string(data)) - } - return validInputs, nil -} - -func crashSHA256(inp string) string { - h := sha256.New() - h.Write([]byte(inp)) - return fmt.Sprintf("%x", h.Sum(nil)) -}