feat(binana): add check command for profiles

This commit is contained in:
phaneron 2025-06-04 17:40:52 -04:00
parent b5ddafb89a
commit ebf88595d9
11 changed files with 151 additions and 39 deletions

4
go.mod
View file

@ -9,11 +9,15 @@ require (
require ( require (
github.com/davecgh/go-spew v1.1.1 // indirect 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/ianlancetaylor/demangle v0.0.0-20240912202439-0a2b6291aafd // indirect
github.com/inconshreveable/mousetrap v1.1.0 // 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/pierrec/lz4/v4 v4.1.21 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sys v0.25.0 // indirect
lukechampine.com/uint128 v1.3.0 // indirect lukechampine.com/uint128 v1.3.0 // indirect
modernc.org/mathutil v1.6.0 // indirect modernc.org/mathutil v1.6.0 // indirect
modernc.org/strutil v1.2.0 // indirect modernc.org/strutil v1.2.0 // indirect

11
go.sum
View file

@ -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/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 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 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 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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 h1:EVX1s+XNss9jkRW9K6XGJn2jL2lB1h5H804oKPsxOec=
github.com/ianlancetaylor/demangle v0.0.0-20240912202439-0a2b6291aafd/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= 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 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 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 h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 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= 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/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 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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/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= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo= lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=

View file

@ -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(&params)
}
var check_cmd = cobra.Command{
Use: "check profile",
Short: "check a profile for correctness",
Run: check_run,
}

View file

@ -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) format_symbols(os.Stdin, os.Stdout)
} }
var format_symbols_cmd = &cobra.Command{ var format_symbols_cmd = cobra.Command{
Use: "format-symbols", Use: "format-symbols",
Short: "format symbols from stdin", Short: "format symbols from stdin",
Run: format_symbols_func, Run: format_symbols_run,
} }

View file

@ -8,13 +8,7 @@ import (
"github.com/thunderbrewhq/binana/go/profile" "github.com/thunderbrewhq/binana/go/profile"
) )
var generate = &cobra.Command{ func generate_run(cmd *cobra.Command, args []string) {
Use: "generate",
Short: "Convert source files into various tool formats",
Run: generate_func,
}
func generate_func(cmd *cobra.Command, args []string) {
compress, err := cmd.Flags().GetBool("compress") compress, err := cmd.Flags().GetBool("compress")
if err != nil { if err != nil {
panic(err) panic(err)
@ -35,3 +29,9 @@ func generate_func(cmd *cobra.Command, args []string) {
os.Exit(1) os.Exit(1)
} }
} }
var generate_cmd = cobra.Command{
Use: "generate",
Short: "Convert source files into various tool formats",
Run: generate_run,
}

View file

@ -10,7 +10,7 @@ import (
) )
// rootCmd represents the base command when called without any subcommands // rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{ var root = cobra.Command{
Use: "binana", Use: "binana",
Short: "Binana helper tool", 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. // 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. // This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() { func Execute() {
err := rootCmd.Execute() err := root.Execute()
if err != nil { if err != nil {
os.Exit(1) os.Exit(1)
} }
} }
func init() { func init() {
// Here you will define your flags and configuration settings. generate_cmd.Flags().StringP("profile", "p", "3.3.5a", "the game profile")
// Cobra supports persistent flags, which, if defined here, generate_cmd.Flags().BoolP("compress", "c", true, "enable/disable compression of the x64dbg database file")
// will be global for your application.
// 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 root.AddCommand(&generate_cmd)
// when this action is called directly. root.AddCommand(&format_symbols_cmd)
// rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") root.AddCommand(&x64dbg_typesort_cmd)
root.AddCommand(&check_cmd)
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)
} }

View file

@ -8,13 +8,13 @@ import (
"github.com/thunderbrewhq/binana/go/x64dbg" "github.com/thunderbrewhq/binana/go/x64dbg"
) )
var x64dbg_typesort = &cobra.Command{ var x64dbg_typesort_cmd = cobra.Command{
Use: "x64dbg-typesort [types.json file]", Use: "x64dbg-typesort [types.json file]",
Short: "sort a x64dbg types 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]) types, err := x64dbg.LoadTypes(args[0])
if err != nil { if err != nil {
panic(err) panic(err)

View file

@ -23,17 +23,19 @@ var (
) )
type info_schema struct { type info_schema struct {
OS string `json:"os"` OS string `json:"os"`
Arch string `json:"arch"` Arch string `json:"arch"`
ModuleName string `json:"module_name"` ModuleName string `json:"module_name"`
ModuleBase string `json:"module_base"` ModuleBase string `json:"module_base"`
FunctionCount uint64 `json:"function_count"`
} }
type Info struct { type Info struct {
OS string OS string
Arch string Arch string
ModuleName string ModuleName string
ModuleBase uint64 ModuleBase uint64
FunctionCount uint64
} }
func read_info(filename string, info *Info) (err error) { func read_info(filename string, info *Info) (err error) {
@ -68,5 +70,7 @@ func read_info(filename string, info *Info) (err error) {
return return
} }
info.FunctionCount = is.FunctionCount
return return
} }

View file

@ -33,7 +33,7 @@ func (profile *Profile) generate_x64dbg_database(compress bool) (err error) {
relative_end_address -= 1 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) fmt.Printf("Strange symbol %s %08x %08x (offset %d)\n", entry.Name, relative_start_address, relative_end_address, relative_end_address-relative_start_address)
} }

View file

@ -13,7 +13,7 @@ const min_columns = 3
type loader struct { type loader struct {
input *bufio.Reader input *bufio.Reader
table Table table Table
line_number int line_number uint64
} }
func (l *loader) read_line() (line string, err error) { 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) err = fmt.Errorf("%w: line %d", err, l.line_number)
return return
} }
entry.LineNumber = l.line_number
err = l.table.Insert(&entry) err = l.table.Insert(&entry)
return return

View file

@ -22,6 +22,7 @@ var (
// An entry in the table // An entry in the table
type Entry struct { type Entry struct {
LineNumber uint64
// Undecorated, raw name // Undecorated, raw name
Name string Name string
// Offset to the start of the function or data // Offset to the start of the function or data