├── .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 | GitHub License 8 | GitHub Repo stars
9 | GitHub forks 10 | GitHub watchers 11 | GitHub contributors 12 |

13 | 14 | ## Description 15 | 16 | SugarFree is an open-source tool designed to analyze and reduce the entropy of a provided PE file. 17 | 18 | ![Static Badge](https://img.shields.io/badge/Go-lang-cyan?style=flat&logoSize=auto) 19 | ![Static Badge](https://img.shields.io/badge/Version-2.0%20(Ocean%20Words)-red?link=https%3A%2F%2Fgithub.com%2Fnickvourd%2FSugarFree%2Freleases) 20 | 21 | SugarFree uses two different techniques (strategies) to reduce the entropy of a PE file: 22 | 23 | - `zero`: Appends null bytes (`0x00`) to the end of the file, increasing its size while lowering entropy. 24 | - `word`: Appends random English words in byte format to the end of the file, increasing its size while lowering entropy. 25 | 26 | The following list explains the meaning of each SugarFree command: 27 | 28 | - `info`: Calculates the entropy of a PE file and its sections. 29 | - `free`: Lowers the overall entropy of a PE file. 30 | 31 | SugarFree is written in Golang, a cross-platform language, enabling its use on both Windows and Linux systems. 32 | 33 | > If you find any bugs, don’t hesitate to [report them](https://github.com/nickvourd/SugarFree/issues). Your feedback is valuable in improving the quality of this project! 34 | 35 | ## Disclaimer 36 | 37 | The authors and contributors of this project are not liable for any illegal use of the tool. It is intended for educational purposes only. Users are responsible for ensuring lawful usage. 38 | 39 | ## Table of Contents 40 | - [SugarFree](#sugarfree) 41 | - [Description](#description) 42 | - [Disclaimer](#disclaimer) 43 | - [Table of Contents](#table-of-contents) 44 | - [Acknowledgement](#acknowledgement) 45 | - [Installation](#installation) 46 | - [Usage](#usage) 47 | - [References](#references) 48 | 49 | ## Acknowledgement 50 | 51 | This project created with :heart: by [@nickvourd](https://x.com/nickvourd) && [@IAMCOMPROMISED](https://x.com/IAMCOMPROMISED). 52 | 53 | Special thanks to my friend [Marios Gyftos](https://www.linkedin.com/in/marios-gyftos-a6b62122/) for inspiring the concept of automated stages. 54 | 55 | ## Installation 56 | 57 | You can use the [precompiled binaries](https://github.com/nickvourd/SugarFree/releases), or you can manually install SugarFree by following the next steps: 58 | 59 | 1) Clone the repository by executing the following command: 60 | 61 | ``` 62 | git clone https://github.com/nickvourd/SugarFree.git 63 | ``` 64 | 65 | 2) Once the repository is cloned, navigate into the SugarFree directory: 66 | 67 | ``` 68 | cd SugarFree 69 | ``` 70 | 71 | 3) Install the third-party dependencies: 72 | 73 | ``` 74 | go mod download 75 | ``` 76 | 77 | 4) Build SugarFree with the following command: 78 | 79 | ``` 80 | go build SugarFree 81 | ``` 82 | 83 | ## Usage 84 | 85 | :information_source: Please refer to the [SugarFree Wiki](https://github.com/nickvourd/SugarFree/wiki) for detailed usage instructions and examples of commands. 86 | 87 | ``` 88 | ███████╗██╗ ██╗ ██████╗ █████╗ ██████╗ ███████╗██████╗ ███████╗███████╗ 89 | ██╔════╝██║ ██║██╔════╝ ██╔══██╗██╔══██╗██╔════╝██╔══██╗██╔════╝██╔════╝ 90 | ███████╗██║ ██║██║ ███╗███████║██████╔╝█████╗ ██████╔╝█████╗ █████╗ 91 | ╚════██║██║ ██║██║ ██║██╔══██║██╔══██╗██╔══╝ ██╔══██╗██╔══╝ ██╔══╝ 92 | ███████║╚██████╔╝╚██████╔╝██║ ██║██║ ██║██║ ██║ ██║███████╗███████╗ 93 | ╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝ 94 | 95 | SugarFree v2.0 - Less sugar (entropy) for your binaries. 96 | SugarFree is an open source tool licensed under MIT. 97 | Written with <3 by @nickvourd && @IAMCOMPROMISED... 98 | Please visit https://github.com/nickvourd/SugarFree for more... 99 | 100 | Usage: 101 | SugarFree [flags] 102 | SugarFree [command] 103 | 104 | Aliases: 105 | SugarFree, sugarfree, SUGARFREE, sf 106 | 107 | Available Commands: 108 | free Free command 109 | help Help about any command 110 | info Info command 111 | 112 | Flags: 113 | -h, --help help for SugarFree 114 | -v, --version Show SugarFree current version 115 | ``` 116 | 117 | ## References 118 | 119 | - [Threat Hunting with File Entropy by Practical Security Analytics LLC](https://practicalsecurityanalytics.com/file-entropy/) 120 | - [Using Entropy in Threat Hunting: a Mathematical Search for the Unknown by Red Canary](https://redcanary.com/blog/threat-detection/threat-hunting-entropy/) 121 | - [EntropyReducer GitHub by Maldev Academy](https://github.com/Maldev-Academy/EntropyReducer) 122 | - [EntropyFix GitHub by EspressoCake](https://github.com/EspressoCake/EntropyFix) 123 | -------------------------------------------------------------------------------- /SugarFree.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "SugarFree/Packages/Arguments" 5 | "SugarFree/Packages/Utils" 6 | "log" 7 | "os" 8 | ) 9 | 10 | // main function 11 | func main() { 12 | logger := log.New(os.Stderr, "[!] ", 0) 13 | 14 | // Call function named CheckGoVersion 15 | Utils.CheckGoVersion() 16 | 17 | // SugarFreeCli Execute 18 | err := Arguments.SugarFreeCli.Execute() 19 | if err != nil { 20 | logger.Fatal("Error: ", err) 21 | return 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module SugarFree 2 | 3 | go 1.23.4 4 | 5 | require ( 6 | github.com/fatih/color v1.18.0 7 | github.com/spf13/cobra v1.9.1 8 | gonum.org/v1/plot v0.15.0 9 | ) 10 | 11 | require ( 12 | git.sr.ht/~sbinet/gg v0.6.0 // indirect 13 | github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b // indirect 14 | github.com/campoy/embedmd v1.0.0 // indirect 15 | github.com/go-fonts/liberation v0.3.3 // indirect 16 | github.com/go-latex/latex v0.0.0-20240709081214-31cef3c7570e // indirect 17 | github.com/go-pdf/fpdf v0.9.0 // indirect 18 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 19 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 20 | github.com/mattn/go-colorable v0.1.14 // indirect 21 | github.com/mattn/go-isatty v0.0.20 // indirect 22 | github.com/pmezard/go-difflib v1.0.0 // indirect 23 | github.com/spf13/pflag v1.0.6 // indirect 24 | golang.org/x/image v0.24.0 // indirect 25 | golang.org/x/sys v0.30.0 // indirect 26 | golang.org/x/text v0.22.0 // indirect 27 | ) 28 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | git.sr.ht/~sbinet/gg v0.6.0 h1:RIzgkizAk+9r7uPzf/VfbJHBMKUr0F5hRFxTUGMnt38= 2 | git.sr.ht/~sbinet/gg v0.6.0/go.mod h1:uucygbfC9wVPQIfrmwM2et0imr8L7KQWywX0xpFMm94= 3 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 4 | github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= 5 | github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= 6 | github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= 7 | github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= 8 | github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY= 9 | github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= 10 | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 11 | github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= 12 | github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= 13 | github.com/go-fonts/liberation v0.3.3 h1:tM/T2vEOhjia6v5krQu8SDDegfH1SfXVRUNNKpq0Usk= 14 | github.com/go-fonts/liberation v0.3.3/go.mod h1:eUAzNRuJnpSnd1sm2EyloQfSOT79pdw7X7++Ri+3MCU= 15 | github.com/go-latex/latex v0.0.0-20240709081214-31cef3c7570e h1:xcdj0LWnMSIU1j8+jIeJyfvk6SjgJedFQssSqFthJ2E= 16 | github.com/go-latex/latex v0.0.0-20240709081214-31cef3c7570e/go.mod h1:J4SAGzkcl+28QWi7yz72tyC/4aGnppOvya+AEv4TaAQ= 17 | github.com/go-pdf/fpdf v0.9.0 h1:PPvSaUuo1iMi9KkaAn90NuKi+P4gwMedWPHhj8YlJQw= 18 | github.com/go-pdf/fpdf v0.9.0/go.mod h1:oO8N111TkmKb9D7VvWGLvLJlaZUQVPM+6V42pp3iV4Y= 19 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= 20 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 21 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 22 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 23 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 24 | github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= 25 | github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= 26 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 27 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 28 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 29 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 30 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 31 | github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= 32 | github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= 33 | github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= 34 | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 35 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 36 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 37 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 38 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 39 | golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78= 40 | golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ= 41 | golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8= 42 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 43 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 44 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 45 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 46 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 47 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 48 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 49 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 50 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 51 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 52 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 53 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 54 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 55 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 56 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 57 | golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 58 | golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= 59 | golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= 60 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 61 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 62 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 63 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 64 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 65 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 66 | gonum.org/v1/plot v0.15.0 h1:SIFtFNdZNWLRDRVjD6CYxdawcpJDWySZehJGpv1ukkw= 67 | gonum.org/v1/plot v0.15.0/go.mod h1:3Nx4m77J4T/ayr/b8dQ8uGRmZF6H3eTqliUExDrQHnM= 68 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 69 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 70 | honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= 71 | --------------------------------------------------------------------------------