Skip to content

Commit

Permalink
change binary detection algorithm
Browse files Browse the repository at this point in the history
Signed-off-by: Rumen Vasilev <[email protected]>
  • Loading branch information
rumenvasilev committed Nov 12, 2023
1 parent 3ece7d4 commit fcecf4c
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 18 deletions.
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ all: pretty build
build: clean
@GOOS=$(target_os) GOARCH=$(target_arch) go build -mod vendor -o ./bin/$(pkg)-$(target_os)

linux:
@GOOS=linux GOARCH=$(target_arch) go build -mod vendor -o ./bin/$(pkg)-linux

windows:
@GOOS=windows GOARCH=$(target_arch) go build -mod vendor -o ./bin/$(pkg)-windows.exe

## Clean binaries
clean:
@rm -rf ./bin
Expand Down
3 changes: 2 additions & 1 deletion internal/core/analysis.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,8 @@ func isIgnoredFile(cfgScanTests bool, cfgMaxFileSize int64, fullFilePath string,
}

// Check if it is a binary file
if util.IsBinaryFile(fullFilePath) {
yes, err = util.IsBinaryFile(fullFilePath)
if yes || err != nil {
return true, "is a binary file, ignoring"
}

Expand Down
60 changes: 48 additions & 12 deletions internal/util/io.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ import (
cp "github.com/otiai10/copy"
)

var magicNumbers = [][]byte{
{0x1F, 0x8B, 0x08, 0x00}, // GZip
{0x42, 0x5A, 0x68, 0x32}, // BZip2
{0x50, 0x4B, 0x03, 0x04}, // ZIP
{0x89, 0x50, 0x4E, 0x47}, // PNG
{0x4D, 0x5A}, // Windows EXE
{0x7F, 'E', 'L', 'F'}, // Linux ELF Executable
{0xFE, 0xED, 0xFA, 0xCE, 0xCE, 0xFA, 0xED, 0xFE}, // macOS Mach-O Binary
{0xFE, 0xED, 0xFA, 0xCF, 0x0C, 0x00, 0x00, 0x01}, // Mach-O 64-bit (x86_64)
}

// TODO THIS FUNC HAS TO RETURN ERROR, OTHERWISE WE DO THE SAME CHECK AGAIN LATER
// PathExists will check if a path exists or not and is used to validate user input
func PathExists(path string) bool {
Expand Down Expand Up @@ -211,24 +222,49 @@ func GetSignatureFiles(dir string) ([]string, error) {
return sigs, nil
}

func IsBinaryFile(path string) bool {
buf := make([]byte, 512)

f, err := os.Open(path)
func IsBinaryFile(filePath string) (bool, error) {
file, err := os.Open(filePath)
if err != nil {
// couldn't open file, exit with false
return false
return false, err
}
defer f.Close()
defer file.Close()

_, err = f.Read(buf)
// Read the first 4 bytes to identify file type
buffer := make([]byte, 4)
_, err = file.Read(buffer)
if err != nil {
// couldn't read file, exit with false
return false
return false, err
}

// Check for common binary file magic numbers
for _, magic := range magicNumbers {
if bytesMatch(buffer, magic) {
return true, nil
}
}

// The implementation above doesn't catch all binaries, one example being go compiled
// binaries for darwin (macos), but unicode test does.
// https://groups.google.com/g/golang-nuts/c/YeLL7L7SwWs/m/LGlsc9GIJlUJ
// if the encoding is invalid, it returns (RuneError, 1)
runerr, res := utf8.DecodeRune(buf)
return (res == 1 && runerr == utf8.RuneError)
runerr, p := utf8.DecodeRune(buffer)
if runerr == utf8.RuneError {
if p == 0 || p == 1 {
return true, nil
}
}

return false, nil
}

func bytesMatch(a, b []byte) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
37 changes: 32 additions & 5 deletions internal/util/io_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,29 @@ func TestFileExists(t *testing.T) {
}

func Test_IsBinaryFile(t *testing.T) {
assert.False(t, IsBinaryFile("./strings.go"))
bin := findBinary(t)
require.NotEmpty(t, bin, "Make sure you've run `make build` before running this test. It relies on the binary being present in bin directory.")
assert.True(t, IsBinaryFile(bin), fmt.Sprintf("Evaluated binary file: %q", bin))
assert.False(t, IsBinaryFile("some/unexisting/file"))
tests := []struct {
name string
input string
want bool
wantErr string
}{
{"strings.go", "./strings.go", false, ""},
{"compiled binary", findBinary(t), true, ""},
{"non-existing file", "some/unexisting/file", false, "open some/unexisting/file: no such file or directory"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.NotEmpty(t, tt.input, "Make sure you've run `make build` before running this test. It relies on the binary being present in bin directory.")
got, err := IsBinaryFile(tt.input)
if tt.wantErr != "" {
assert.Error(t, err)
assert.EqualError(t, err, tt.wantErr)
} else {
assert.NoError(t, err)
}
assert.Equal(t, tt.want, got)
})
}
}

// finds the pre-built binary under the name <root>/bin/rvsecret*
Expand All @@ -65,6 +83,7 @@ func findBinary(t *testing.T) string {
continue
} else if entry.Type().IsRegular() {
if strings.Contains(entry.Name(), "rvsecret") {
t.Logf("Found binary name %q in path %q", entry.Name(), binPath)
binaryName = fmt.Sprintf("%s/%s", binPath, entry.Name())
break
}
Expand All @@ -74,6 +93,14 @@ func findBinary(t *testing.T) string {
return binaryName
}

// go test -run XXX -bench=. -benchmem
// goos: darwin
// goarch: amd64
// pkg: github.com/rumenvasilev/rvsecret/internal/util
// cpu: Intel(R) Core(TM) i7-5557U CPU @ 3.10GHz
// BenchmarkIsBinaryFile-4 92380 12687 ns/op 136 B/op 3 allocs/op
// PASS
// ok github.com/rumenvasilev/rvsecret/internal/util 1.323s
func BenchmarkIsBinaryFile(b *testing.B) {
for n := 0; n < b.N; n++ {
IsBinaryFile("../../bin/rvsecret-darwin")
Expand Down

0 comments on commit fcecf4c

Please sign in to comment.