Skip to content

Commit cfb5b25

Browse files
authored
Merge pull request #53 from ac0d3r/main
Add coverage guided with Stalker
2 parents 6aed48f + 6d1e02d commit cfb5b25

12 files changed

Lines changed: 361 additions & 71 deletions

File tree

.gitignore

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# If you prefer the allow list template instead of the deny list, see community template:
2+
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
3+
#
4+
# Binaries for programs and plugins
5+
*.exe
6+
*.exe~
7+
*.dll
8+
*.so
9+
*.dylib
10+
11+
# Test binary, built with `go test -c`
12+
*.test
13+
14+
# Code coverage profiles and other test artifacts
15+
*.out
16+
coverage.*
17+
*.coverprofile
18+
profile.cov
19+
20+
# Dependency directories (remove the comment below to include it)
21+
# vendor/
22+
23+
# Go workspace file
24+
go.work
25+
go.work.sum
26+
27+
# env file
28+
.env
29+
30+
# Editor/IDE
31+
# .idea/
32+
.vscode/
33+
node_modules/
34+
# macOS
35+
.DS_Store
36+
# dev
37+
_agent.js

cmd/fuzz.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import (
44
"encoding/json"
55
"errors"
66
"fmt"
7-
"github.com/fatih/color"
8-
"github.com/nsecho/furlzz/internal/config"
97
"os"
108
"path/filepath"
119
"regexp"
@@ -14,9 +12,12 @@ import (
1412
"strings"
1513
"time"
1614

15+
"github.com/fatih/color"
1716
"github.com/frida/frida-go/frida"
18-
"github.com/nsecho/furlzz/mutator"
1917
"github.com/spf13/cobra"
18+
19+
"github.com/nsecho/furlzz/internal/config"
20+
"github.com/nsecho/furlzz/mutator"
2021
)
2122

2223
var (
@@ -36,6 +37,11 @@ var fuzzCmd = &cobra.Command{
3637
return err
3738
}
3839

40+
debug, err := cmd.Flags().GetBool("debug")
41+
if err != nil {
42+
return err
43+
}
44+
3945
var cfg config.Config
4046
f, err := os.Open(configPath)
4147
if err != nil {
@@ -193,7 +199,7 @@ var fuzzCmd = &cobra.Command{
193199
delegateName = fuzzMap["delegate"].(string)
194200
}
195201

196-
_ = script.ExportsCall("setup_fuzz", method, uiapp, delegateName, sceneName)
202+
_ = script.ExportsCall("setup_fuzz", method, uiapp, delegateName, sceneName, debug)
197203

198204
l.Infof("Finished fuzz setup")
199205

@@ -208,10 +214,19 @@ var fuzzCmd = &cobra.Command{
208214
case mutated := <-ch:
209215
lastInput = mutated.Input
210216
l.Infof("[%s] %s\n", color.New(color.FgCyan).Sprintf("%s", mutated.Mutation), mutated.Input)
217+
211218
_ = script.ExportsCall("fuzz", cfg.Type, mutated.Input)
219+
212220
if cfg.Timeout > 0 {
213221
time.Sleep(time.Duration(cfg.Timeout) * time.Second)
214222
}
223+
224+
// Check if script has new coverage blocks
225+
has, ok := script.ExportsCall("has_new_blocks").(bool)
226+
if ok && has {
227+
l.Infof("New blocks found, continuing fuzzing...")
228+
mut.HandleNewCoverage(mutated.MutatedInputs)
229+
}
215230
}
216231
}
217232

@@ -275,6 +290,7 @@ func spawnApp(dev frida.DeviceInt, app string, toSpawn bool, sTimeout uint) erro
275290

276291
func init() {
277292
fuzzCmd.Flags().StringP("config", "c", "furlzz.json", "Path to config file")
293+
fuzzCmd.Flags().BoolP("debug", "d", true, "Enable debug output (useful for coverage)")
278294

279295
rootCmd.AddCommand(fuzzCmd)
280296
}

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6
1313
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
1414
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
1515
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
16-
github.com/frida/frida-go v0.12.0 h1:3cONEUvLTGvna67VmcVr/4Lle8Ec3rsDgi2rEOPbdmg=
17-
github.com/frida/frida-go v0.12.0/go.mod h1:OyRIp58wuTcGggI6ztakXcHkTXrt5WfD9yi3pq6QyGw=
1816
github.com/frida/frida-go v0.13.1 h1:RN097XqOvKAb3wnYocSJzCC+KMvc6Ks8ePbIrReu00w=
1917
github.com/frida/frida-go v0.13.1/go.mod h1:O8Dg1YBGfQsBEL1a8x3GURw/JllJrcuvg78ga2OgdM4=
2018
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=

main.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ package main
33
import (
44
_ "embed"
55
"fmt"
6-
"github.com/frida/frida-go/frida"
7-
"github.com/nsecho/furlzz/cmd"
8-
"io/ioutil"
96
"os"
107
"os/exec"
118
"path/filepath"
9+
10+
"github.com/frida/frida-go/frida"
11+
"github.com/nsecho/furlzz/cmd"
1212
)
1313

1414
const (
@@ -73,7 +73,7 @@ func main() {
7373
sc = bundle
7474
}
7575
} else {
76-
scriptContent, err = ioutil.ReadFile(filepath.Join(tempDir, agentFilename))
76+
scriptContent, err = os.ReadFile(filepath.Join(tempDir, agentFilename))
7777
if err != nil {
7878
fmt.Fprintf(os.Stderr, "Failed to read agent: %v\n", err)
7979
os.Exit(1)

mutator/helpers.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"io"
55
"os"
66
"path/filepath"
7+
"slices"
78
)
89

910
func (m *Mutator) getFuzzedInput(set string) string {
@@ -17,10 +18,33 @@ func (m *Mutator) getFuzzedInput(set string) string {
1718
}
1819

1920
func (m *Mutator) fetchInput(set string) string {
21+
m.mux.RLock()
22+
defer m.mux.RUnlock()
23+
2024
k := m.r.Intn(len(m.inputSets[set]))
2125
return m.inputSets[set][k]
2226
}
2327

28+
func (m *Mutator) addCorpus(set, value string) {
29+
m.mux.Lock()
30+
defer m.mux.Unlock()
31+
32+
if set == "" || value == "" {
33+
return
34+
}
35+
36+
if m.inputSets != nil {
37+
if slices.Contains(m.inputSets[set], value) {
38+
return
39+
}
40+
41+
if _, e := m.inputSets[set]; !e {
42+
m.inputSets[set] = []string{}
43+
}
44+
m.inputSets[set] = append(m.inputSets[set], value)
45+
}
46+
}
47+
2448
func readCrashes(app string) ([]string, error) {
2549
files, _ := filepath.Glob("fcrash_*_*")
2650

mutator/mutator.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"math/rand"
66
"strings"
7+
"sync"
78
"time"
89
)
910

@@ -40,6 +41,7 @@ func NewMutator(inp, app string, runs uint, fnName string, ignoreCrashes bool, i
4041
}
4142

4243
type Mutator struct {
44+
mux sync.RWMutex
4345
fuzzIdx int
4446
runs uint
4547
baseURL string
@@ -57,8 +59,9 @@ type Mutator struct {
5759
}
5860

5961
type Mutated struct {
60-
Input string
61-
Mutation string
62+
Input string
63+
Mutation string
64+
MutatedInputs []string
6265
}
6366

6467
func (m *Mutator) Close() {
@@ -152,9 +155,19 @@ func (m *Mutator) mutateAndSend() bool {
152155
}
153156

154157
m.ch <- &Mutated{
155-
Input: inp,
156-
Mutation: method,
158+
Input: inp,
159+
Mutation: method,
160+
MutatedInputs: mutatedInputs,
157161
}
158162
m.lastInput = strings.Join(mutatedInputs, "")
159163
return true
160164
}
165+
166+
func (m *Mutator) HandleNewCoverage(mutatedInputs []string) {
167+
if len(mutatedInputs) == 0 {
168+
return
169+
}
170+
for i, input := range mutatedInputs {
171+
m.addCorpus(fmt.Sprintf("FUZZ%d", i+1), input)
172+
}
173+
}

mutator/mutator_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package mutator
2+
3+
import (
4+
"log"
5+
"testing"
6+
)
7+
8+
func TestHandleNewCoverage(t *testing.T) {
9+
initialCorpus := map[string][]string{
10+
"FUZZ1": {"test"},
11+
"FUZZ2": {"123"},
12+
}
13+
14+
m := NewMutator(
15+
"user=FUZZ1 pass=FUZZ2",
16+
"myapp",
17+
11,
18+
"url",
19+
false,
20+
initialCorpus,
21+
)
22+
defer m.Close()
23+
24+
ch := m.Mutate()
25+
26+
count := 0
27+
for {
28+
select {
29+
case mutated := <-ch:
30+
if mutated == nil {
31+
return
32+
}
33+
34+
t.Log("Mutated Input:", mutated.Input)
35+
if count%5 == 0 {
36+
// simulate has new coverage path
37+
log.Println("Simulate has new coverage path")
38+
m.HandleNewCoverage(mutated.MutatedInputs)
39+
log.Println("corpus", m.inputSets)
40+
}
41+
count++
42+
case <-m.quit:
43+
return
44+
}
45+
}
46+
}

package-lock.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{
22
"name": "furlzz",
33
"version": "1.0.0",
4-
"type":"module",
4+
"type": "module",
55
"scripts": {
6-
"test": "echo \"Error: no test specified\" && exit 1"
6+
"build": "frida-compile -S -c script.ts -o _agent.js"
77
},
88
"repository": {
99
"type": "git",
@@ -17,5 +17,8 @@
1717
"homepage": "https://github.com/NSEcho/furlzz#readme",
1818
"dependencies": {
1919
"frida-objc-bridge": "^8.0.4"
20+
},
21+
"devDependencies": {
22+
"@types/frida-gum": "^19.0.0"
2023
}
21-
}
24+
}

0 commit comments

Comments
 (0)