diff --git a/go.mod b/go.mod index d579be7..11f8411 100644 --- a/go.mod +++ b/go.mod @@ -9,11 +9,15 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fatih/color v1.18.0 // indirect github.com/ianlancetaylor/demangle v0.0.0-20240912202439-0a2b6291aafd // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/sys v0.25.0 // indirect lukechampine.com/uint128 v1.3.0 // indirect modernc.org/mathutil v1.6.0 // indirect modernc.org/strutil v1.2.0 // indirect diff --git a/go.sum b/go.sum index 8beb669..a0834e6 100644 --- a/go.sum +++ b/go.sum @@ -3,12 +3,19 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/ianlancetaylor/demangle v0.0.0-20240912202439-0a2b6291aafd h1:EVX1s+XNss9jkRW9K6XGJn2jL2lB1h5H804oKPsxOec= github.com/ianlancetaylor/demangle v0.0.0-20240912202439-0a2b6291aafd/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= @@ -20,6 +27,10 @@ github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo= diff --git a/go/cmd/binana/cmd/check.go b/go/cmd/binana/cmd/check.go new file mode 100644 index 0000000..de5a54a --- /dev/null +++ b/go/cmd/binana/cmd/check.go @@ -0,0 +1,97 @@ +package cmd + +import ( + "fmt" + "strings" + + "github.com/fatih/color" + "github.com/spf13/cobra" + "github.com/thunderbrewhq/binana/go/profile" + "github.com/thunderbrewhq/binana/go/symfile" +) + +type check_params struct { + ProfileName string + Constructors bool + Bounds bool +} + +func check(params *check_params) { + p, err := profile.Open(params.ProfileName) + if err != nil { + panic(err) + } + + named_function_count := uint64(0) + + warnings := uint64(0) + + warn := func(s *symfile.Entry, f string, args ...any) { + warnings++ + color.Set(color.FgRed) + fmt.Printf(" warning: ") + color.Unset() + fmt.Printf("in line %d: (%s): ", s.LineNumber, s.Name) + fmt.Printf(f, args...) + } + + for _, s := range p.SymbolTable.Entries { + sn := s.Name + + if s.Kind == symfile.Function { + named_function_count++ + + if params.Constructors { + // + b, a, found := strings.Cut(sn, "__") + if found { + if b == a { + warn(&s, "this style of naming a constructor function is preferred: ClassName__constructor\n") + } + } + } + + if params.Bounds { + if s.EndAddress == 0 { + warn(&s, "does not have an end address\n") + } + } + } + } + + if p.Info.FunctionCount != 0 { + ratio := float64(named_function_count) / float64(p.Info.FunctionCount) + fmt.Printf("%d out of %d functions named (%f%%)\n", named_function_count, p.Info.FunctionCount, ratio*100.0) + fmt.Printf("%d warnings generated\n", warnings) + } +} + +func check_run(cmd *cobra.Command, args []string) { + if len(args) < 1 { + cmd.Help() + return + } + f := cmd.Flags() + var params check_params + params.ProfileName = args[0] + + var err error + + params.Constructors, err = f.GetBool("constructors") + if err != nil { + panic(err) + } + + params.Bounds, err = f.GetBool("bounds") + if err != nil { + panic(err) + } + + check(¶ms) +} + +var check_cmd = cobra.Command{ + Use: "check profile", + Short: "check a profile for correctness", + Run: check_run, +} diff --git a/go/cmd/binana/cmd/format_symbols.go b/go/cmd/binana/cmd/format_symbols.go index 8f35893..0e101f6 100644 --- a/go/cmd/binana/cmd/format_symbols.go +++ b/go/cmd/binana/cmd/format_symbols.go @@ -81,12 +81,12 @@ func format_symbols(in io.Reader, out io.Writer) { } } -func format_symbols_func(cmd *cobra.Command, args []string) { +func format_symbols_run(cmd *cobra.Command, args []string) { format_symbols(os.Stdin, os.Stdout) } -var format_symbols_cmd = &cobra.Command{ +var format_symbols_cmd = cobra.Command{ Use: "format-symbols", Short: "format symbols from stdin", - Run: format_symbols_func, + Run: format_symbols_run, } diff --git a/go/cmd/binana/cmd/generate.go b/go/cmd/binana/cmd/generate.go index e24dbc5..7b25ef4 100644 --- a/go/cmd/binana/cmd/generate.go +++ b/go/cmd/binana/cmd/generate.go @@ -8,13 +8,7 @@ import ( "github.com/thunderbrewhq/binana/go/profile" ) -var generate = &cobra.Command{ - Use: "generate", - Short: "Convert source files into various tool formats", - Run: generate_func, -} - -func generate_func(cmd *cobra.Command, args []string) { +func generate_run(cmd *cobra.Command, args []string) { compress, err := cmd.Flags().GetBool("compress") if err != nil { panic(err) @@ -35,3 +29,9 @@ func generate_func(cmd *cobra.Command, args []string) { os.Exit(1) } } + +var generate_cmd = cobra.Command{ + Use: "generate", + Short: "Convert source files into various tool formats", + Run: generate_run, +} diff --git a/go/cmd/binana/cmd/root.go b/go/cmd/binana/cmd/root.go index 123218b..22d24bb 100644 --- a/go/cmd/binana/cmd/root.go +++ b/go/cmd/binana/cmd/root.go @@ -10,7 +10,7 @@ import ( ) // rootCmd represents the base command when called without any subcommands -var rootCmd = &cobra.Command{ +var root = cobra.Command{ Use: "binana", Short: "Binana helper tool", } @@ -18,27 +18,21 @@ var rootCmd = &cobra.Command{ // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { - err := rootCmd.Execute() + err := root.Execute() if err != nil { os.Exit(1) } } func init() { - // Here you will define your flags and configuration settings. - // Cobra supports persistent flags, which, if defined here, - // will be global for your application. + generate_cmd.Flags().StringP("profile", "p", "3.3.5a", "the game profile") + generate_cmd.Flags().BoolP("compress", "c", true, "enable/disable compression of the x64dbg database file") - // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.binana.yaml)") + check_cmd.Flags().Bool("bounds", false, "check for bad function boundaries") + check_cmd.Flags().Bool("constructors", false, "check for outdated class constructor names") - // Cobra also supports local flags, which will only run - // when this action is called directly. - // rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") - - generate.Flags().StringP("profile", "p", "3.3.5a", "the game profile") - generate.Flags().BoolP("compress", "c", true, "enable/disable compression of the x64dbg database file") - - rootCmd.AddCommand(generate) - rootCmd.AddCommand(format_symbols_cmd) - rootCmd.AddCommand(x64dbg_typesort) + root.AddCommand(&generate_cmd) + root.AddCommand(&format_symbols_cmd) + root.AddCommand(&x64dbg_typesort_cmd) + root.AddCommand(&check_cmd) } diff --git a/go/cmd/binana/cmd/x64dbg_typesort.go b/go/cmd/binana/cmd/x64dbg_typesort.go index 9a64f25..01ee0e0 100644 --- a/go/cmd/binana/cmd/x64dbg_typesort.go +++ b/go/cmd/binana/cmd/x64dbg_typesort.go @@ -8,13 +8,13 @@ import ( "github.com/thunderbrewhq/binana/go/x64dbg" ) -var x64dbg_typesort = &cobra.Command{ +var x64dbg_typesort_cmd = cobra.Command{ Use: "x64dbg-typesort [types.json file]", Short: "sort a x64dbg types file", - Run: x64dbg_typesort_func, + Run: x64dbg_typesort_run, } -func x64dbg_typesort_func(cmd *cobra.Command, args []string) { +func x64dbg_typesort_run(cmd *cobra.Command, args []string) { types, err := x64dbg.LoadTypes(args[0]) if err != nil { panic(err) diff --git a/go/profile/info.go b/go/profile/info.go index 1d086a4..a891176 100644 --- a/go/profile/info.go +++ b/go/profile/info.go @@ -23,17 +23,19 @@ var ( ) type info_schema struct { - OS string `json:"os"` - Arch string `json:"arch"` - ModuleName string `json:"module_name"` - ModuleBase string `json:"module_base"` + OS string `json:"os"` + Arch string `json:"arch"` + ModuleName string `json:"module_name"` + ModuleBase string `json:"module_base"` + FunctionCount uint64 `json:"function_count"` } type Info struct { - OS string - Arch string - ModuleName string - ModuleBase uint64 + OS string + Arch string + ModuleName string + ModuleBase uint64 + FunctionCount uint64 } func read_info(filename string, info *Info) (err error) { @@ -68,5 +70,7 @@ func read_info(filename string, info *Info) (err error) { return } + info.FunctionCount = is.FunctionCount + return } diff --git a/go/profile/x64dbg_generate_database.go b/go/profile/x64dbg_generate_database.go index bc54b29..f25847a 100644 --- a/go/profile/x64dbg_generate_database.go +++ b/go/profile/x64dbg_generate_database.go @@ -33,7 +33,7 @@ func (profile *Profile) generate_x64dbg_database(compress bool) (err error) { relative_end_address -= 1 } - if relative_end_address < relative_start_address || relative_end_address-relative_start_address >= 10000 { + if relative_end_address < relative_start_address || relative_end_address-relative_start_address >= 50000 { fmt.Printf("Strange symbol %s %08x %08x (offset %d)\n", entry.Name, relative_start_address, relative_end_address, relative_end_address-relative_start_address) } diff --git a/go/symfile/loader.go b/go/symfile/loader.go index 3486902..d9a6b77 100644 --- a/go/symfile/loader.go +++ b/go/symfile/loader.go @@ -13,7 +13,7 @@ const min_columns = 3 type loader struct { input *bufio.Reader table Table - line_number int + line_number uint64 } func (l *loader) read_line() (line string, err error) { @@ -32,6 +32,7 @@ func (l *loader) parse_line(line string) (err error) { err = fmt.Errorf("%w: line %d", err, l.line_number) return } + entry.LineNumber = l.line_number err = l.table.Insert(&entry) return diff --git a/go/symfile/symfile.go b/go/symfile/symfile.go index 6b2fcfb..3aaed4e 100644 --- a/go/symfile/symfile.go +++ b/go/symfile/symfile.go @@ -22,6 +22,7 @@ var ( // An entry in the table type Entry struct { + LineNumber uint64 // Undecorated, raw name Name string // Offset to the start of the function or data