├── .github └── workflows │ └── release.yml ├── .gitignore ├── LICENSE ├── Packages ├── Arguments │ ├── cmd.go │ ├── free.go │ └── info.go ├── Calculate │ └── Calculate.go ├── Colors │ └── Colors.go ├── Output │ └── Output.go ├── Reduce │ └── Reduce.go ├── Utils │ └── Utils.go └── WordList │ └── WordList.go ├── Pictures └── Logo.png ├── README.md ├── SugarFree.go ├── go.mod └── go.sum /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # .github/workflows/release.yml 2 | name: goreleaser 3 | 4 | on: 5 | pull_request: 6 | push: 7 | # run only against tags 8 | tags: 9 | - "*" 10 | 11 | permissions: 12 | contents: write 13 | # packages: write 14 | # issues: write 15 | # id-token: write 16 | 17 | jobs: 18 | goreleaser: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | with: 24 | fetch-depth: 0 25 | - name: Set up Go 26 | uses: actions/setup-go@v5 27 | with: 28 | go-version: stable 29 | # More assembly might be required: Docker logins, GPG, etc. 30 | # It all depends on your needs. 31 | - name: Run GoReleaser 32 | uses: goreleaser/goreleaser-action@v6 33 | with: 34 | # either 'goreleaser' (default) or 'goreleaser-pro' 35 | distribution: goreleaser 36 | # 'latest', 'nightly', or a semver 37 | version: "~> v2" 38 | args: release --clean 39 | env: 40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 41 | # Your GoReleaser Pro key, if you are using the 'goreleaser-pro' distribution 42 | # GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 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 | *.txt 11 | *.dll 12 | *.exe 13 | *SugarFree 14 | 15 | # Test binary, built with `go test -c` 16 | *.test 17 | 18 | # Output of the go coverage tool, specifically when used with LiteIDE 19 | *.out 20 | 21 | # Dependency directories (remove the comment below to include it) 22 | # vendor/ 23 | 24 | # Go workspace file 25 | go.work 26 | go.work.sum 27 | 28 | # env file 29 | .env 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Nikos Vourdas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Packages/Arguments/cmd.go: -------------------------------------------------------------------------------- 1 | package Arguments 2 | 3 | import ( 4 | "SugarFree/Packages/Colors" 5 | "fmt" 6 | "log" 7 | "os" 8 | 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | var ( 13 | __version__ = "2.0" 14 | __license__ = "MIT" 15 | __author__ = []string{"@nickvourd", "@IAMCOMPROMISED"} 16 | __github__ = "https://github.com/nickvourd/SugarFree" 17 | __version_name__ = "Ocean Words" 18 | __ascii__ = ` 19 | 20 | ███████╗██╗ ██╗ ██████╗ █████╗ ██████╗ ███████╗██████╗ ███████╗███████╗ 21 | ██╔════╝██║ ██║██╔════╝ ██╔══██╗██╔══██╗██╔════╝██╔══██╗██╔════╝██╔════╝ 22 | ███████╗██║ ██║██║ ███╗███████║██████╔╝█████╗ ██████╔╝█████╗ █████╗ 23 | ╚════██║██║ ██║██║ ██║██╔══██║██╔══██╗██╔══╝ ██╔══██╗██╔══╝ ██╔══╝ 24 | ███████║╚██████╔╝╚██████╔╝██║ ██║██║ ██║██║ ██║ ██║███████╗███████╗ 25 | ╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝ 26 | ` 27 | 28 | __text__ = ` 29 | SugarFree v%s - Less sugar (entropy) for your binaries. 30 | SugarFree is an open source tool licensed under %s. 31 | Written with <3 by %s && %s... 32 | Please visit %s for more... 33 | 34 | ` 35 | 36 | SugarFreeCli = &cobra.Command{ 37 | Use: "SugarFree", 38 | SilenceUsage: true, 39 | RunE: StartSugarFree, 40 | Aliases: []string{"sugarfree", "SUGARFREE", "sf"}, 41 | } 42 | ) 43 | 44 | // ShowAscii function 45 | func ShowAscii() { 46 | // Initialize RandomColor 47 | randomColor := Colors.RandomColor() 48 | fmt.Print(randomColor(__ascii__)) 49 | fmt.Printf(__text__, __version__, __license__, __author__[0], __author__[1], __github__) 50 | } 51 | 52 | // init function 53 | // init all flags. 54 | func init() { 55 | // Disable default command completion for SugarFree CLI. 56 | SugarFreeCli.CompletionOptions.DisableDefaultCmd = true 57 | 58 | // Add commands to the SugarFree CLI. 59 | SugarFreeCli.Flags().SortFlags = true 60 | SugarFreeCli.Flags().BoolP("version", "v", false, "Show SugarFree current version") 61 | SugarFreeCli.AddCommand(infoArgument) 62 | SugarFreeCli.AddCommand(freeArgument) 63 | 64 | // Add flags to the 'info' command. 65 | infoArgument.Flags().SortFlags = true 66 | infoArgument.Flags().StringP("file", "f", "", "Set input file") 67 | infoArgument.Flags().StringP("output", "o", "", "Save results to output file") 68 | 69 | // Add flags to the 'free' command. 70 | freeArgument.Flags().SortFlags = true 71 | freeArgument.Flags().StringP("file", "f", "", "Set input file") 72 | freeArgument.Flags().Float64P("target", "t", 4.6, "Set target entropy value to achieve") 73 | freeArgument.Flags().StringP("strategy", "s", "zero", "Set strategy to apply (i.e., zero, word)") 74 | freeArgument.Flags().BoolP("graph", "g", false, "Enable entropy graph") 75 | } 76 | 77 | // ShowVersion function 78 | func ShowVersion(versionFlag bool) { 79 | // if one argument 80 | if versionFlag { 81 | // if version flag exists 82 | fmt.Print("[+] Current version: " + Colors.BoldRed(__version__) + "\n\n[+] Version name: " + Colors.BoldRed(__version_name__) + "\n\n") 83 | os.Exit(0) 84 | } 85 | } 86 | 87 | // StartSugarFree function 88 | func StartSugarFree(cmd *cobra.Command, args []string) error { 89 | logger := log.New(os.Stderr, "[!] ", 0) 90 | 91 | // Call function named ShowAscii 92 | ShowAscii() 93 | 94 | // Check if additional arguments were provided. 95 | if len(os.Args) == 1 { 96 | // Display help message. 97 | err := cmd.Help() 98 | 99 | // If error exists 100 | if err != nil { 101 | logger.Fatal("Error: ", err) 102 | return err 103 | } 104 | } 105 | 106 | // Obtain flag 107 | versionFlag, _ := cmd.Flags().GetBool("version") 108 | 109 | // Call function named ShowVersion 110 | ShowVersion(versionFlag) 111 | 112 | return nil 113 | } 114 | -------------------------------------------------------------------------------- /Packages/Arguments/free.go: -------------------------------------------------------------------------------- 1 | package Arguments 2 | 3 | import ( 4 | "SugarFree/Packages/Calculate" 5 | "SugarFree/Packages/Colors" 6 | "SugarFree/Packages/Reduce" 7 | "SugarFree/Packages/Utils" 8 | "fmt" 9 | "image/color" 10 | "io/ioutil" 11 | "log" 12 | "os" 13 | "strconv" 14 | "strings" 15 | "time" 16 | 17 | "github.com/spf13/cobra" 18 | "gonum.org/v1/plot" 19 | "gonum.org/v1/plot/plotter" 20 | "gonum.org/v1/plot/vg" 21 | ) 22 | 23 | // StageData represents data for each reduction stage 24 | type StageData struct { 25 | stage int 26 | entropy float64 27 | } 28 | 29 | // freeArgument represents the 'free' command in the CLI. 30 | var freeArgument = &cobra.Command{ 31 | // Use defines how the command should be called. 32 | Use: "free", 33 | Short: "Free command", 34 | Long: "Lowers the overall entropy of a PE file", 35 | SilenceUsage: true, 36 | Aliases: []string{"FREE", "Free"}, 37 | 38 | // RunE defines the function to run when the command is executed. 39 | RunE: func(cmd *cobra.Command, args []string) error { 40 | logger := log.New(os.Stderr, "[!] ", 0) 41 | 42 | // Show ASCII banner 43 | ShowAscii() 44 | 45 | // Check if additional arguments were provided 46 | if len(os.Args) <= 2 { 47 | err := cmd.Help() 48 | if err != nil { 49 | logger.Fatal("Error ", err) 50 | return err 51 | } 52 | os.Exit(0) 53 | } 54 | 55 | // Get variables from the command line 56 | file, _ := cmd.Flags().GetString("file") 57 | target, _ := cmd.Flags().GetFloat64("target") 58 | graph, _ := cmd.Flags().GetBool("graph") 59 | strategy, _ := cmd.Flags().GetString("strategy") 60 | 61 | // Check if the file flag is empty 62 | if file == "" { 63 | logger.Fatal("Error: Input file is missing. Please provide it to continue...\n\n") 64 | } 65 | 66 | // Record start time for performance measurement 67 | reductionStartTime := time.Now() 68 | 69 | // Get the current date and time 70 | getDateTime := time.Now().Format("2006-01-02 15:04:05") 71 | 72 | fmt.Printf("[*] Starting PE entropy reduction on %s\n\n", Colors.BoldWhite(getDateTime)) 73 | fmt.Printf("[*] Applied Strategy: %s\n\n", Colors.BoldBlue(strings.ToUpper(strategy))) 74 | 75 | // Get absolute file path 76 | filePath, err := Utils.GetAbsolutePath(file) 77 | if err != nil { 78 | logger.Fatal("Error: ", err) 79 | } 80 | 81 | // Get file size 82 | fileSize, err := Utils.GetFileSize(filePath) 83 | if err != nil { 84 | logger.Fatal("Error: ", err) 85 | } 86 | 87 | // Get filename and extension 88 | fileName, fileExtension := Utils.SplitFileName(file) 89 | 90 | fmt.Printf("[+] Analyzing PE File: %s\n", Colors.BoldCyan(file)) 91 | fmt.Printf("[+] Initial File Size: %s KB\n", Colors.BoldYellow(fileSize)) 92 | 93 | // Read original binary data 94 | originalData, err := ioutil.ReadFile(file) 95 | if err != nil { 96 | fmt.Printf("Error reading input file: %v\n", err) 97 | os.Exit(1) 98 | } 99 | 100 | // Calculate initial entropy 101 | initialEntropy := Calculate.CalculateFullEntropy(originalData) 102 | 103 | // Display initial overall PE entropy 104 | fmt.Printf("[+] Initial Overall PE Entropy: %s\n", Colors.CalculateColor2Entropy(initialEntropy)) 105 | 106 | // Create a slice to store entropy values for each stage 107 | stageData := []StageData{ 108 | {0, initialEntropy}, // Include initial entropy as stage 0 109 | } 110 | 111 | // Copy the original data 112 | modifiedData := make([]byte, len(originalData)) 113 | copy(modifiedData, originalData) 114 | 115 | // Add variables to track progress 116 | currentEntropy := initialEntropy 117 | iterationCount := 0 118 | lastEntropy := currentEntropy 119 | stuckCount := 0 120 | maxIterations := 10 // Maximum number of iterations to prevent infinite loops 121 | 122 | // Loop until we reach target entropy or can't reduce further 123 | for currentEntropy > target && iterationCount < maxIterations { 124 | // Call function named ApplyStrategy 125 | modifiedData = Reduce.ApplyStrategy(modifiedData, 60000, strategy) 126 | 127 | // Calculate new entropy 128 | currentEntropy = Calculate.CalculateFullEntropy(modifiedData) 129 | 130 | // Calculate reduction percentages 131 | totalReductionPercentage := ((initialEntropy - currentEntropy) / initialEntropy) * 100 132 | stageReductionPercentage := ((lastEntropy - currentEntropy) / lastEntropy) * 100 133 | 134 | // Calculate and display progress for this iteration 135 | iterationCount++ 136 | 137 | // Store stage data for graphing 138 | stageData = append(stageData, StageData{ 139 | stage: iterationCount, 140 | entropy: currentEntropy, 141 | }) 142 | 143 | // Convert current entropy to string for filename 144 | stageEntropy := strconv.FormatFloat(currentEntropy, 'f', 5, 64) 145 | 146 | // Build new filename for this stage 147 | stageFileName := Utils.BuildNewName(fileName, fileExtension, stageEntropy) 148 | 149 | // Write stage data to output file 150 | if err := ioutil.WriteFile(stageFileName, modifiedData, 0644); err != nil { 151 | fmt.Printf("[!] Error writing stage file: %v\n", err) 152 | continue 153 | } 154 | 155 | // Get file size for the new file 156 | newFileSize, err := Utils.GetFileSize(stageFileName) 157 | if err != nil { 158 | logger.Fatalf("[!] Error getting file size: %v\n", err) 159 | continue 160 | } 161 | 162 | if iterationCount == 1 { 163 | // For first stage, show entropy and current reduction percentage 164 | fmt.Printf("\n[+] Stage %d Reduction - Overall PE Entropy: %s\n", 165 | iterationCount, 166 | Colors.CalculateColor2Entropy(currentEntropy)) 167 | fmt.Printf("[+] Stage %d Current Reduction Percentage: %s%%\n", 168 | iterationCount, 169 | Colors.BoldMagenta(fmt.Sprintf("%.2f", stageReductionPercentage))) 170 | fmt.Printf("[+] Stage %d File Size: %s KB\n", 171 | iterationCount, 172 | Colors.BoldYellow(newFileSize)) 173 | } else { 174 | // For subsequent stages, show all messages in desired order 175 | fmt.Printf("\n[+] Stage %d Reduction - Overall PE Entropy: %s\n", 176 | iterationCount, 177 | Colors.CalculateColor2Entropy(currentEntropy)) 178 | fmt.Printf("[+] Stage %d Current Reduction Percentage: %s%%\n", 179 | iterationCount, 180 | Colors.BoldMagenta(fmt.Sprintf("%.2f", stageReductionPercentage))) 181 | fmt.Printf("[+] Stage %d File Size: %s KB\n", 182 | iterationCount, 183 | Colors.BoldYellow(newFileSize)) 184 | fmt.Printf("[+] Stage %d Total Reduction Percentage: %s%%\n", 185 | iterationCount, 186 | Colors.BoldBlue(fmt.Sprintf("%.2f", totalReductionPercentage))) 187 | } 188 | 189 | // Get absolute path for stage file 190 | stageFileName, err = Utils.GetAbsolutePath(stageFileName) 191 | if err != nil { 192 | logger.Fatalf("[!] Error getting absolute path for stage file: %v\n", err) 193 | continue 194 | } 195 | 196 | fmt.Printf("[+] Stage %d saved to: %s\n", iterationCount, Colors.BoldCyan(stageFileName)) 197 | 198 | // Check if we're stuck (entropy isn't decreasing significantly) 199 | if lastEntropy-currentEntropy < 0.0001 { 200 | stuckCount++ 201 | if stuckCount >= 3 { // If stuck for 3 iterations, break 202 | fmt.Printf("\n[!] Entropy reduction plateaued after %d stages\n", iterationCount) 203 | break 204 | } 205 | } else { 206 | stuckCount = 0 207 | } 208 | 209 | lastEntropy = currentEntropy 210 | } 211 | 212 | // If graph flag is enabled 213 | if graph { 214 | // Create a new plot 215 | p := plot.New() 216 | 217 | p.Title.Text = "Entropy Reduction Stages" 218 | p.X.Label.Text = "Stage" 219 | p.Y.Label.Text = "Entropy" 220 | 221 | // Create points for the line 222 | pts := make(plotter.XYs, len(stageData)) 223 | for i, data := range stageData { 224 | pts[i].X = float64(data.stage) 225 | pts[i].Y = data.entropy 226 | } 227 | 228 | // Create a line plotter and set its style 229 | line, err := plotter.NewLine(pts) 230 | if err != nil { 231 | logger.Fatalf("\n[!] Error creating line plot: %v\n", err) 232 | } else { 233 | line.Color = color.RGBA{R: 255, B: 0, A: 255} 234 | line.Width = vg.Points(2) 235 | p.Add(line) 236 | 237 | // Add scatter points 238 | scatter, err := plotter.NewScatter(pts) 239 | if err != nil { 240 | logger.Fatalf("\n[!] Error creating scatter plot: %v\n", err) 241 | } else { 242 | scatter.GlyphStyle.Color = color.RGBA{B: 255, A: 255} 243 | scatter.GlyphStyle.Radius = vg.Points(4) 244 | p.Add(scatter) 245 | 246 | getDateTime = time.Now().Format("20060102-150405") 247 | 248 | // Save the plot to a PNG file 249 | outputFile := fmt.Sprintf("%s_Entropy_Reduction_%s.png", fileName, getDateTime) 250 | if err := p.Save(8*vg.Inch, 6*vg.Inch, outputFile); err != nil { 251 | logger.Fatalf("\n[!] Error saving plot: %v\n", err) 252 | } else { 253 | fmt.Printf("\n[+] Entropy reduction graph saved to: %s\n", Colors.BoldYellow(outputFile)) 254 | } 255 | } 256 | } 257 | } 258 | 259 | // Record the end time 260 | reductionEndTime := time.Now() 261 | 262 | // Calculate the duration 263 | reductionDurationTime := reductionEndTime.Sub(reductionStartTime) 264 | 265 | fmt.Printf("\n[*] Completed in: %s\n\n", Colors.BoldWhite(reductionDurationTime)) 266 | 267 | return nil 268 | }, 269 | } 270 | -------------------------------------------------------------------------------- /Packages/Arguments/info.go: -------------------------------------------------------------------------------- 1 | package Arguments 2 | 3 | import ( 4 | "SugarFree/Packages/Calculate" 5 | "SugarFree/Packages/Colors" 6 | "SugarFree/Packages/Output" 7 | "SugarFree/Packages/Utils" 8 | "fmt" 9 | "log" 10 | "os" 11 | "time" 12 | 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | // infoArgument represents the 'info' command in the CLI. 17 | var infoArgument = &cobra.Command{ 18 | // Use defines how the command should be called. 19 | Use: "info", 20 | Short: "Info command", 21 | Long: "Calculates the entropy of a PE file and its sections", 22 | SilenceUsage: true, 23 | Aliases: []string{"INFO", "Info"}, 24 | 25 | // RunE defines the function to run when the command is executed. 26 | RunE: func(cmd *cobra.Command, args []string) error { 27 | logger := log.New(os.Stderr, "[!] ", 0) 28 | 29 | // Call function named ShowAscii 30 | ShowAscii() 31 | 32 | // Check if additional arguments were provided. 33 | if len(os.Args) <= 2 { 34 | // Show help message. 35 | err := cmd.Help() 36 | if err != nil { 37 | logger.Fatal("Error ", err) 38 | return err 39 | } 40 | 41 | // Exit the program. 42 | os.Exit(0) 43 | } 44 | 45 | // Define variables 46 | var sectionEntropy string 47 | 48 | // Get variables from the command line 49 | file, _ := cmd.Flags().GetString("file") 50 | output, _ := cmd.Flags().GetString("output") 51 | 52 | // Check if the file flag is empty 53 | if file == "" { 54 | logger.Fatal("Error: Input file is missing. Please provide it to continue...\n\n") 55 | } 56 | 57 | // Record the start time 58 | calculateStartTime := time.Now() 59 | 60 | // Get the current date and time 61 | getDateTime := time.Now().Format("2006-01-02 15:04:05") 62 | 63 | fmt.Printf("[*] Starting PE analysis on %s\n\n", Colors.BoldWhite(getDateTime)) 64 | 65 | // Call function named GetAbsolutePath 66 | filePath, err := Utils.GetAbsolutePath(file) 67 | if err != nil { 68 | logger.Fatal("Error: ", err) 69 | } 70 | 71 | // Call function named GetFileSize 72 | fileSize, err := Utils.GetFileSize(filePath) 73 | if err != nil { 74 | logger.Fatal("Error: ", err) 75 | } 76 | 77 | // Call function named ReadSections 78 | sections, err := Calculate.ReadSections(filePath) 79 | if err != nil { 80 | log.Fatal(err) 81 | } 82 | 83 | // Convert Calcualte.SectionEntropy to Output.Section 84 | var outputSections []Output.Section 85 | for _, section := range sections { 86 | outputSections = append(outputSections, Output.Section{ 87 | Name: section.Name, 88 | Entropy: section.Entropy, 89 | }) 90 | } 91 | 92 | // Read the file 93 | buf, err := os.ReadFile(file) 94 | if err != nil { 95 | logger.Fatal("Error: ", err) 96 | } 97 | 98 | fullEntropy := Calculate.CalculateFullEntropy(buf) 99 | 100 | // Print the results 101 | fmt.Printf("[+] Analyzing PE File: %s\n", Colors.BoldCyan(file)) 102 | fmt.Printf("[+] File Size: %s KB\n", Colors.BoldYellow(fileSize)) 103 | fmt.Printf("[+] Overall PE Entropy: %s\n\n", Colors.CalculateColor2Entropy(fullEntropy)) 104 | fmt.Print("[+] PE Sections Entropy:\n") 105 | for _, section := range sections { 106 | // Call function ColorManager 107 | sectionName := Colors.ColorNameManager(section.Name) 108 | 109 | // Call function named CalculateColor2Entropy 110 | sectionEntropy = Colors.CalculateColor2Entropy(section.Entropy) 111 | 112 | // Print the results 113 | fmt.Printf(" >>> \"%s\" Scored Entropy Of Value: %s\n", sectionName, sectionEntropy) 114 | } 115 | 116 | // Check if the output flag is empty. 117 | if output != "" { 118 | // Call function named WriteToFile 119 | Output.Write2File(outputSections, output, file, fileSize, fullEntropy, getDateTime) 120 | 121 | // Call function named GetAbsolutePath 122 | outputFilePath, err := Utils.GetAbsolutePath(output) 123 | if err != nil { 124 | logger.Fatal("Error: ", err) 125 | } 126 | 127 | fmt.Printf("\n[+] Results saved to: %s\n", Colors.BoldCyan(outputFilePath)) 128 | } 129 | 130 | // Record the end time 131 | calculateEndTime := time.Now() 132 | 133 | // Calculate the duration 134 | calculateDurationTime := calculateEndTime.Sub(calculateStartTime) 135 | 136 | // Print the duration 137 | fmt.Printf("\n[*] Completed in: %s\n\n", Colors.BoldWhite(calculateDurationTime)) 138 | 139 | return nil 140 | }, 141 | } 142 | -------------------------------------------------------------------------------- /Packages/Calculate/Calculate.go: -------------------------------------------------------------------------------- 1 | package Calculate 2 | 3 | import ( 4 | "debug/pe" 5 | "fmt" 6 | "log" 7 | "math" 8 | "os" 9 | "strings" 10 | ) 11 | 12 | // SectionEntropy struct 13 | type SectionEntropy struct { 14 | Name string // Section name 15 | Entropy float64 // Calculated entropy value 16 | Size int64 // Size of the section in bytes 17 | Offset int64 // File offset of the section 18 | } 19 | 20 | // CalculateFullEntropy function 21 | func CalculateFullEntropy(buffer []byte) float64 { 22 | entropy := 0.0 23 | 24 | // Create a map to count byte occurrences 25 | counts := make(map[byte]int) 26 | for _, b := range buffer { 27 | counts[b]++ 28 | } 29 | 30 | // Calculate entropy using Shannon's formula 31 | bufferLen := float64(len(buffer)) 32 | for _, count := range counts { 33 | p := float64(count) / bufferLen 34 | entropy += -p * math.Log2(p) 35 | } 36 | 37 | return entropy 38 | } 39 | 40 | // CalculateSectionEntropy function 41 | func CalculateSectionEntropy(data []byte) float64 { 42 | // Handle empty data case 43 | if len(data) == 0 { 44 | return 0 45 | } 46 | 47 | var ( 48 | byteCounts [256]int 49 | dataLength = float64(len(data)) 50 | entropy float64 51 | ) 52 | 53 | // Count occurrences of each byte value 54 | for _, b := range data { 55 | byteCounts[b]++ 56 | } 57 | 58 | // Calculate Shannon entropy 59 | for _, count := range byteCounts { 60 | if count > 0 { 61 | probability := float64(count) / dataLength 62 | entropy -= probability * math.Log2(probability) 63 | } 64 | } 65 | 66 | // Ensure entropy stays within valid range 67 | if entropy < 0 { 68 | log.Printf("Warning: Negative entropy calculated, adjusting to 0") 69 | return 0 70 | } 71 | if entropy > 8 { 72 | log.Printf("Warning: Entropy exceeded maximum possible value, adjusting to 8") 73 | return 8 74 | } 75 | 76 | return entropy 77 | } 78 | 79 | // ReadSections function 80 | func ReadSections(filePath string) ([]SectionEntropy, error) { 81 | // Open the PE file 82 | file, err := os.Open(filePath) 83 | if err != nil { 84 | return nil, fmt.Errorf("failed to open file: %w", err) 85 | } 86 | defer file.Close() 87 | 88 | // Parse the PE file structure 89 | peFile, err := pe.NewFile(file) 90 | if err != nil { 91 | return nil, fmt.Errorf("failed to parse PE file: %w", err) 92 | } 93 | 94 | var sectionEntropies []SectionEntropy 95 | 96 | // Process each section 97 | for _, section := range peFile.Sections { 98 | // Skip sections with zero size 99 | if section.Size == 0 { 100 | log.Printf("Warning: Skipping zero-size section %s\n", section.Name) 101 | continue 102 | } 103 | 104 | // Allocate buffer for section data 105 | rawData := make([]byte, section.Size) 106 | 107 | // Read section data using correct file offset 108 | n, err := file.ReadAt(rawData, int64(section.Offset)) 109 | if err != nil { 110 | // Special handling for .reloc section which often has special requirements 111 | if strings.EqualFold(section.Name, ".reloc") { 112 | log.Printf("Warning: Incomplete read of .reloc section: %v\n", err) 113 | if n == 0 { 114 | continue 115 | } 116 | // Use partial data if some was read 117 | rawData = rawData[:n] 118 | } else { 119 | // For other sections, report the error 120 | return nil, fmt.Errorf("failed to read section %s: %w", section.Name, err) 121 | } 122 | } 123 | 124 | // Call function named CalculateSectionEntropy 125 | entropy := CalculateSectionEntropy(rawData[:n]) 126 | 127 | // Store section information 128 | sectionEntropies = append(sectionEntropies, SectionEntropy{ 129 | Name: string(section.Name), 130 | Entropy: entropy, 131 | Size: int64(n), 132 | Offset: int64(section.Offset), 133 | }) 134 | } 135 | 136 | return sectionEntropies, nil 137 | } 138 | -------------------------------------------------------------------------------- /Packages/Colors/Colors.go: -------------------------------------------------------------------------------- 1 | package Colors 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "strings" 7 | "time" 8 | 9 | "github.com/fatih/color" 10 | ) 11 | 12 | var ( 13 | // Bold Colors 14 | BoldBlue = color.New(color.FgBlue, color.Bold).SprintFunc() 15 | BoldRed = color.New(color.FgRed, color.Bold).SprintFunc() 16 | BoldGreen = color.New(color.FgGreen, color.Bold).SprintFunc() 17 | BoldYellow = color.New(color.FgYellow, color.Bold).SprintFunc() 18 | BoldWhite = color.New(color.FgHiWhite, color.Bold).SprintFunc() 19 | BoldMagenta = color.New(color.FgMagenta, color.Bold).SprintFunc() 20 | BoldCyan = color.New(color.FgCyan, color.Bold).SprintFunc() 21 | ) 22 | 23 | // Define a slice containing all available color functions 24 | var allColors = []func(a ...interface{}) string{ 25 | BoldBlue, BoldRed, BoldGreen, BoldYellow, BoldWhite, BoldMagenta, BoldCyan, 26 | } 27 | 28 | // RandomColor function 29 | // RandomColor selects a random color function from the available ones 30 | func RandomColor() func(a ...interface{}) string { 31 | rand.Seed(time.Now().UnixNano()) 32 | return allColors[rand.Intn(len(allColors))] 33 | } 34 | 35 | // ColorNameManager function 36 | func ColorNameManager(section string) string { 37 | // Determine color based on section name 38 | switch strings.ToLower(section) { 39 | case ".text": 40 | return BoldYellow(section) // Code section 41 | case ".data": 42 | return BoldMagenta(section) // Data section 43 | case ".rdata": 44 | return BoldBlue(section) // Read-only data 45 | case ".pdata": 46 | return BoldCyan(section) // Exception handling data 47 | case ".bss": 48 | return BoldWhite(section) // Uninitialized data 49 | case ".idata": 50 | return BoldYellow(section) // Import directory 51 | case ".edata": 52 | return BoldMagenta(section) // Export directory 53 | case ".rsrc": 54 | return BoldBlue(section) // Resources 55 | case ".tls": 56 | return BoldCyan(section) // Thread-local storage 57 | case ".reloc": 58 | return BoldWhite(section) // Relocations 59 | case ".debug": 60 | return BoldYellow(section) // Debug information 61 | case ".xdata": 62 | return BoldMagenta(section) // Exception information 63 | default: 64 | return BoldBlue(section) // Unknown sections 65 | } 66 | } 67 | 68 | // CalculateColor2Entropy function 69 | func CalculateColor2Entropy(entropy float64) string { 70 | // Check if the entropy is less than 5.0 71 | if entropy < 5.0 { 72 | return BoldGreen(fmt.Sprintf("%.5f", entropy)) 73 | } 74 | 75 | return BoldRed(fmt.Sprintf("%.5f", entropy)) 76 | } 77 | -------------------------------------------------------------------------------- /Packages/Output/Output.go: -------------------------------------------------------------------------------- 1 | package Output 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | // Section struct 9 | type Section struct { 10 | Name string 11 | Entropy float64 12 | } 13 | 14 | // Write2File function 15 | func Write2File(sections []Section, filePath string, fileName string, fileSize float64, fullEntropy float64, getDateTime string) { 16 | file, err := os.Create(filePath) 17 | if err != nil { 18 | fmt.Println("Error opening file:", err) 19 | return 20 | } 21 | defer file.Close() 22 | 23 | WriteBasicInfo(file, fileName, fileSize, fullEntropy, getDateTime) 24 | WriteSectionInfo(file, sections) 25 | } 26 | 27 | // writeBasicInfo writes basic file information 28 | func WriteBasicInfo(file *os.File, fileName string, fileSize float64, entropy float64, getDateTime string) { 29 | fmt.Fprintf(file, "PE Analysis Report - %s\n\n", getDateTime) 30 | fmt.Fprintf(file, "File Name: %s\n", fileName) 31 | fmt.Fprintf(file, "File Size: %f bytes\n", fileSize) 32 | fmt.Fprintf(file, "Overall PE Entropy: %.5f\n", entropy) 33 | } 34 | 35 | // writeSectionInfo writes detailed section information 36 | func WriteSectionInfo(file *os.File, sections []Section) { 37 | fmt.Fprintln(file, "\nPE Sections Entropy:") 38 | for _, section := range sections { 39 | fmt.Fprintf(file, " >>> \"%s\" Entropy: %.5f\n", section.Name, section.Entropy) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Packages/Reduce/Reduce.go: -------------------------------------------------------------------------------- 1 | package Reduce 2 | 3 | import ( 4 | "SugarFree/Packages/WordList" 5 | "log" 6 | "strings" 7 | ) 8 | 9 | // ApplyStrategy function 10 | func ApplyStrategy(binaryData []byte, number int, strategy string) []byte { 11 | switch strings.ToLower(strategy) { 12 | case "zero": 13 | zeroBytes := make([]byte, number) 14 | //fmt.Println(zeroBytes) 15 | 16 | result := make([]byte, len(binaryData)+number) 17 | copy(result, binaryData) 18 | copy(result[len(binaryData):], zeroBytes) 19 | 20 | return result 21 | case "word": 22 | // Call function named SelectWords 23 | words := WordList.SelectWords(number) 24 | //fmt.Print(words) 25 | 26 | wordsBytes := []byte(strings.Join(words, "")) 27 | //fmt.Print(wordsBytes) 28 | 29 | result := append(binaryData, wordsBytes...) 30 | 31 | return result 32 | default: 33 | log.Fatal("Error: Invalid strategy provided. Please provide a valid strategy to continue...\n\n") 34 | return nil 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Packages/Utils/Utils.go: -------------------------------------------------------------------------------- 1 | package Utils 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "path/filepath" 8 | "runtime" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | // CheckGoVersio function 14 | func CheckGoVersion() { 15 | version := runtime.Version() 16 | version = strings.Replace(version, "go1.", "", -1) 17 | verNumb, _ := strconv.ParseFloat(version, 64) 18 | if verNumb < 19.1 { 19 | logger := log.New(os.Stderr, "[!] ", 0) 20 | logger.Fatal("The version of Go is to old, please update to version 1.19.1 or later...\n") 21 | } 22 | } 23 | 24 | // GetAbsolutePath function 25 | func GetAbsolutePath(filename string) (string, error) { 26 | // Get the absolute path of the file 27 | absolutePath, err := filepath.Abs(filename) 28 | if err != nil { 29 | return "", err 30 | } 31 | return absolutePath, nil 32 | } 33 | 34 | // GetFileSize function 35 | func GetFileSize(filePath string) (float64, error) { 36 | // Open the file 37 | file, err := os.Open(filePath) 38 | if err != nil { 39 | return 0, fmt.Errorf("failed to open file: %v", err) 40 | } 41 | defer file.Close() 42 | 43 | // Get file information 44 | fileInfo, err := file.Stat() 45 | if err != nil { 46 | return 0, fmt.Errorf("failed to get file info: %v", err) 47 | } 48 | 49 | // Convert bytes to KB (divide by 1024) 50 | sizeInKB := float64(fileInfo.Size()) / 1024.0 51 | 52 | // Return the file size in KB 53 | return sizeInKB, nil 54 | } 55 | 56 | // SplitFileName function 57 | func SplitFileName(filename string) (name, extension string) { 58 | // Find the last occurrence of "." 59 | lastDot := strings.LastIndex(filename, ".") 60 | 61 | // If there's no dot or the dot is the first character 62 | if lastDot <= 0 { 63 | return filename, "" 64 | } 65 | 66 | // Split the filename 67 | name = filename[:lastDot] 68 | extension = filename[lastDot+1:] 69 | 70 | return name, extension 71 | } 72 | 73 | // BuildNewName function 74 | func BuildNewName(name, extension, additionalName string) string { 75 | // Handle cases where extension might or might not have a dot 76 | ext := extension 77 | if extension != "" && !strings.HasPrefix(extension, ".") { 78 | ext = "." + extension 79 | } 80 | 81 | // If additionalName is a string containing a number 82 | value, err := strconv.ParseFloat(additionalName, 64) 83 | if err != nil { 84 | // Handle error if the string can't be converted to float 85 | log.Fatal("Error converting string to float: ", err) 86 | return "" 87 | } 88 | 89 | additionalName = fmt.Sprintf("%.5f", value) 90 | 91 | // Build the new name: name_additionalName.extension 92 | return fmt.Sprintf("%s_%s%s", name, additionalName, ext) 93 | } 94 | -------------------------------------------------------------------------------- /Packages/WordList/WordList.go: -------------------------------------------------------------------------------- 1 | package WordList 2 | 3 | import ( 4 | "math/rand" 5 | "strings" 6 | ) 7 | 8 | // Predefined list of unique English words (expand as needed) 9 | var englishWords = []string{ 10 | "aaron", "abandoned", "abdomen", "aberdeen", "abilities", 11 | "ability", "aboriginal", "about", "above", "abraham", 12 | "absence", "absolute", "absolutely", "absorb", "abstract", 13 | "absurd", "abuse", "academic", "academy", "accelerate", 14 | "accent", "acceptable", "acceptance", "access", "accessible", 15 | "accessories", "accident", "accommodate", "accommodation", "accompany", 16 | // Add more words as needed 17 | } 18 | 19 | // SelectWords function 20 | func SelectWords(numWords int) []string { 21 | if numWords <= len(englishWords) { 22 | // Shuffle and take the first numWords 23 | rand.Shuffle(len(englishWords), func(i, j int) { 24 | englishWords[i], englishWords[j] = englishWords[j], englishWords[i] 25 | }) 26 | return englishWords[:numWords] 27 | } 28 | 29 | // If we need more words than available, generate additional random words 30 | result := make([]string, len(englishWords)) 31 | copy(result, englishWords) 32 | 33 | // Create a map for faster lookup 34 | wordMap := make(map[string]bool) 35 | for _, word := range result { 36 | wordMap[word] = true 37 | } 38 | 39 | // Generate additional random words 40 | chars := "abcdefghijklmnopqrstuvwxyz" 41 | for len(result) < numWords { 42 | wordLength := rand.Intn(7) + 4 // Random length between 4 and 10 43 | var word strings.Builder 44 | for i := 0; i < wordLength; i++ { 45 | word.WriteByte(chars[rand.Intn(len(chars))]) 46 | } 47 | 48 | newWord := word.String() 49 | if !wordMap[newWord] { 50 | result = append(result, newWord) 51 | wordMap[newWord] = true 52 | } 53 | } 54 | 55 | return result 56 | } 57 | -------------------------------------------------------------------------------- /Pictures/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickvourd/SugarFree/fd6a76530c207cd204ce79e477f1869da5054785/Pictures/Logo.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SugarFree 2 | 3 | Less sugar (entropy) for your binaries 4 | 5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |