Skip to content

Commit

Permalink
feat: add subcommands
Browse files Browse the repository at this point in the history
  • Loading branch information
NSEcho committed Sep 13, 2023
1 parent 16db668 commit 5397bc7
Show file tree
Hide file tree
Showing 7 changed files with 378 additions and 201 deletions.
87 changes: 87 additions & 0 deletions cmd/crash.go
Original file line number Diff line number Diff line change
@@ -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)
}
177 changes: 177 additions & 0 deletions cmd/fuzz.go
Original file line number Diff line number Diff line change
@@ -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)
}
66 changes: 66 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -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))
}
44 changes: 44 additions & 0 deletions cmd/session.go
Original file line number Diff line number Diff line change
@@ -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)
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Loading

0 comments on commit 5397bc7

Please sign in to comment.