// main.go package main import ( "bytes" "fmt" "io" "log" "os" "path/filepath" "strconv" "strings" "github.com/glaslos/ssdeep" ) const blockSize = 1024 * 1024 // main is the entry point of the program. func main() { // 1. Get and validate command-line arguments if len(os.Args) != 3 { usage := ` Binwally (Go Version): Binary and Directory tree comparison tool using the Fuzzy Hashing concept (ssdeep) Usage: go run . ` fmt.Printf(usage) os.Exit(1) } path1 := os.Args[1] path2 := os.Args[2] info1, err1 := os.Stat(path1) if err1 != nil { log.Fatalf("Invalid path: %s", path1) } info2, err2 := os.Stat(path2) if err2 != nil { log.Fatalf("Invalid path: %s", path2) } // 2. Route to appropriate comparison logic if info1.IsDir() && info2.IsDir() { // Both are directories var diffs []int fmt.Println("\nSCORE RESULT PATH") compareTrees(path1, path2, &diffs) if len(diffs) == 0 { fmt.Println("No diffs found\n") } else { totalScore := 0 for _, score := range diffs { totalScore += score } averageScore := totalScore / len(diffs) fmt.Println("\nTotal files compared:", len(diffs)) fmt.Printf("Overall match score: %d%%\n\n", averageScore) } } else if !info1.IsDir() && !info2.IsDir() { // Both are files hash1, err := ssdeep.HashFromFile(path1) // CORRECTED if err != nil { log.Fatalf("Failed to hash file %s: %v", path1, err) } hash2, err := ssdeep.HashFromFile(path2) // CORRECTED if err != nil { log.Fatalf("Failed to hash file %s: %v", path2, err) } score, err := ssdeep.Distance(hash1, hash2) // CORRECTED if err != nil { log.Fatalf("Failed to compare hashes: %v", err) } fmt.Printf("Overall match score: %d%%\n\n", score) } else { log.Fatal("Error: Both arguments must be files or both must be directories.") } } // compareTrees recursively compares two directory trees. func compareTrees(dir1, dir2 string, diffs *[]int) { // 1. Read contents of both directories entries1, err := os.ReadDir(dir1) if err != nil { log.Printf("Error reading directory %s: %v", dir1, err) return } entries2, err := os.ReadDir(dir2) if err != nil { log.Printf("Error reading directory %s: %v", dir2, err) return } // Create maps for quick lookup by name map1 := make(map[string]os.DirEntry) for _, e := range entries1 { map1[e.Name()] = e } map2 := make(map[string]os.DirEntry) for _, e := range entries2 { map2[e.Name()] = e } // 2. Find and report unique files/dirs reportUniques(map1, map2, dir1, " <<< unique ", diffs) // In dir1 only reportUniques(map2, map1, dir2, " >>> unique ", diffs) // In dir2 only // 3. Process common items for name, e1 := range map1 { if e2, ok := map2[name]; ok { // Name exists in both directories path1 := filepath.Join(dir1, name) path2 := filepath.Join(dir2, name) isDir1 := e1.IsDir() isDir2 := e2.IsDir() if isDir1 && isDir2 { // Both are directories, recurse compareTrees(path1, path2, diffs) } else if !isDir1 && !isDir2 { // Both are files, compare them compareFiles(path1, path2, diffs) } else { // Mismatched types (file vs dir) or symlinks fmt.Printf(" - ignored %s (symlink or type mismatch)\n", path1) *diffs = append(*diffs, 100) // Original script counts this as 100 } } } } // reportUniques finds items in map1 not in map2 and reports them. func reportUniques(map1, map2 map[string]os.DirEntry, baseDir, prefix string, diffs *[]int) { for name, entry := range map1 { if _, exists := map2[name]; !exists { fullPath := filepath.Join(baseDir, name) if !entry.IsDir() { // It's a unique file fmt.Println(prefix, fullPath) *diffs = append(*diffs, 0) } else { // It's a unique directory, walk it and report all files within filepath.WalkDir(fullPath, func(path string, d os.DirEntry, err error) error { if err != nil { return err } if !d.IsDir() { fmt.Println(prefix, path) *diffs = append(*diffs, 0) } return nil }) } } } } // compareFiles compares two files, first byte-by-byte, then with ssdeep if different. func compareFiles(path1, path2 string, diffs *[]int) { f1, err := os.Open(path1) if err != nil { log.Printf("Error opening file %s: %v", path1, err) return } defer f1.Close() f2, err := os.Open(path2) if err != nil { log.Printf("Error opening file %s: %v", path2, err) return } defer f2.Close() // Compare files chunk by chunk for { b1 := make([]byte, blockSize) _, err1 := f1.Read(b1) b2 := make([]byte, blockSize) _, err2 := f2.Read(b2) if err1 == io.EOF && err2 == io.EOF { // Files are identical fmt.Printf("%5s matches %s\n", "100", getRelativePath(path1)) *diffs = append(*diffs, 100) return } if err1 != nil && err1 != io.EOF || err2 != nil && err2 != io.EOF { log.Printf("Error reading from files: %v, %v", err1, err2) return } if !bytes.Equal(b1, b2) { // Files differ, use ssdeep hash1, _ := ssdeep.HashFromFile(path1) // CORRECTED hash2, _ := ssdeep.HashFromFile(path2) // CORRECTED score, _ := ssdeep.Distance(hash1, hash2) // CORRECTED fmt.Printf("%5s differs %s\n", strconv.Itoa(score), getRelativePath(path1)) *diffs = append(*diffs, score) return } } } // getRelativePath tries to mimic the Python script's path trimming. func getRelativePath(path string) string { parts := strings.Split(filepath.ToSlash(path), "/") if len(parts) > 1 { return strings.Join(parts[1:], "/") } return path }