diff --git a/go.sum b/go.sum index ba62a6b..d6a6f69 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +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/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= diff --git a/go/cmd/binana/cmd/root.go b/go/cmd/binana/cmd/root.go index 93d0e91..393ae59 100644 --- a/go/cmd/binana/cmd/root.go +++ b/go/cmd/binana/cmd/root.go @@ -40,4 +40,5 @@ func init() { x64dbg_gen.Flags().StringP("base-address", "b", "00400000", "the base address of the module") rootCmd.AddCommand(x64dbg_gen) + rootCmd.AddCommand(x64dbg_typesort) } diff --git a/go/cmd/binana/cmd/x64dbg_typesort.go b/go/cmd/binana/cmd/x64dbg_typesort.go new file mode 100644 index 0000000..9a64f25 --- /dev/null +++ b/go/cmd/binana/cmd/x64dbg_typesort.go @@ -0,0 +1,32 @@ +package cmd + +import ( + "encoding/json" + "os" + + "github.com/spf13/cobra" + "github.com/thunderbrewhq/binana/go/x64dbg" +) + +var x64dbg_typesort = &cobra.Command{ + Use: "x64dbg-typesort [types.json file]", + Short: "sort a x64dbg types file", + Run: x64dbg_typesort_func, +} + +func x64dbg_typesort_func(cmd *cobra.Command, args []string) { + types, err := x64dbg.LoadTypes(args[0]) + if err != nil { + panic(err) + } + + if err = x64dbg.SortTypes(types); err != nil { + panic(err) + } + + encoder := json.NewEncoder(os.Stdout) + encoder.SetIndent("", " ") + if err = encoder.Encode(types); err != nil { + panic(err) + } +} diff --git a/go/profile/x64dbg_generate_types.go b/go/profile/x64dbg_generate_types.go index 39ae8fc..3cb5688 100644 --- a/go/profile/x64dbg_generate_types.go +++ b/go/profile/x64dbg_generate_types.go @@ -55,7 +55,7 @@ loop: return } -func cc_type_to_typedef(t cc.Type) (m x64dbg.Type) { +func cc_type_to_typedef(t cc.Type) (m x64dbg.AliasType) { var s string var arrsize int32 = 1 var array bool @@ -156,7 +156,7 @@ func (profile *Profile) generate_x64dbg_types() (err error) { if declarator, ok := node.(*cc.Declarator); ok { if declarator.IsTypedefName { if declarator.Type().Kind() != cc.Struct { - var x64_type x64dbg.Type = cc_type_to_typedef(declarator.Type()) + var x64_type x64dbg.AliasType = cc_type_to_typedef(declarator.Type()) x64_type.Name = scope_id.String() if !slices.Contains(ignore_types, x64_type.Name) { x64_types.Types = append(x64_types.Types, x64_type) @@ -180,6 +180,7 @@ func (profile *Profile) generate_x64dbg_types() (err error) { var x64_struct x64dbg.StructType x64_struct.Name = struct_name.String() + x64_struct.Size = int32(struct_type.Size()) for i := range struct_type.NumField() { struct_member := struct_type.FieldByIndex([]int{i}) @@ -199,7 +200,7 @@ func (profile *Profile) generate_x64dbg_types() (err error) { for i := range struct_member.Type().NumField() { union_field := struct_member.Type().FieldByIndex([]int{i}) - var x64_union_member x64dbg.Type = cc_type_to_typedef(union_field.Type()) + var x64_union_member x64dbg.AliasType = cc_type_to_typedef(union_field.Type()) x64_union_member.Name = union_field.Name().String() x64_union.Members = append(x64_union.Members, x64_union_member) } @@ -210,11 +211,13 @@ func (profile *Profile) generate_x64dbg_types() (err error) { x64_struct_member.Type = union_type_name x64_struct_member.Name = struct_member.Name().String() x64_struct_member.Offset = int32(struct_member.Offset()) + // x64_struct_member.Offset = -1 x64_struct.Members = append(x64_struct.Members, x64_struct_member) } else { x64_struct_member := cc_type_to_struct_member_type(struct_member.Type()) x64_struct_member.Name = struct_member.Name().String() + // x64_struct_member.Offset = -1 x64_struct_member.Offset = int32(struct_member.Offset()) x64_struct.Members = append(x64_struct.Members, x64_struct_member) } @@ -225,6 +228,11 @@ func (profile *Profile) generate_x64dbg_types() (err error) { types_file_path := filepath.Join(profile.Directory, "x32dbg", "types.json") + err = x64dbg.SortTypes(&x64_types) + if err != nil { + return + } + err = x64dbg.SaveTypes(types_file_path, &x64_types) if err != nil { return diff --git a/go/x64dbg/alias_type.go b/go/x64dbg/alias_type.go new file mode 100644 index 0000000..cb74fb2 --- /dev/null +++ b/go/x64dbg/alias_type.go @@ -0,0 +1,17 @@ +package x64dbg + +type AliasType struct { + Type string `json:"type"` + Name string `json:"name"` + ArraySize int32 `json:"arrsize,omitempty"` +} + +func (alias *AliasType) GetName() string { + return alias.Name +} + +func (alias *AliasType) Dependencies() []string { + return []string{ + alias.Type, + } +} diff --git a/go/x64dbg/function_type.go b/go/x64dbg/function_type.go new file mode 100644 index 0000000..493cad5 --- /dev/null +++ b/go/x64dbg/function_type.go @@ -0,0 +1,23 @@ +package x64dbg + +type FunctionType struct { + ReturnType string `json:"rettype"` + CallConvention string `json:"callconv"` + NoReturn bool `json:"noreturn"` + Name string `json:"name"` + Arguments []AliasType `json:"arguments,omitempty"` +} + +func (function *FunctionType) GetName() string { + return function.Name +} + +func (function *FunctionType) Dependencies() (s []string) { + if function.ReturnType != "" { + s = append(s, function.ReturnType) + } + for _, t := range function.Arguments { + s = append(s, t.Type) + } + return +} diff --git a/go/x64dbg/sort_types.go b/go/x64dbg/sort_types.go new file mode 100644 index 0000000..8cb596d --- /dev/null +++ b/go/x64dbg/sort_types.go @@ -0,0 +1,17 @@ +package x64dbg + +func SortTypes(types *Types) (err error) { + graph := new_type_dependency_graph() + if err = graph.Load(types); err != nil { + return + } + + var sorted *Types + sorted, err = graph.Save() + if err != nil { + return + } + + *types = *sorted + return +} diff --git a/go/x64dbg/struct_type.go b/go/x64dbg/struct_type.go new file mode 100644 index 0000000..edfd471 --- /dev/null +++ b/go/x64dbg/struct_type.go @@ -0,0 +1,26 @@ +package x64dbg + +type StructMemberType struct { + Type string `json:"type"` + Name string `json:"name"` + ArraySize int32 `json:"arrsize,omitempty"` + Offset int32 `json:"offset"` +} + +type StructType struct { + Name string `json:"name"` + Size int32 `json:"size"` + Members []StructMemberType `json:"members,omitempty"` +} + +func (struct_ *StructType) GetName() string { + return struct_.Name +} + +func (struct_ *StructType) Dependencies() (s []string) { + for _, member := range struct_.Members { + s = append(s, member.Type) + } + + return +} diff --git a/go/x64dbg/type_dependency_graph.go b/go/x64dbg/type_dependency_graph.go new file mode 100644 index 0000000..b2948ae --- /dev/null +++ b/go/x64dbg/type_dependency_graph.go @@ -0,0 +1,257 @@ +package x64dbg + +import ( + "fmt" + "slices" + "sort" +) + +type type_dependency_graph struct { + all_types map[string]*type_graph_node +} + +func (graph *type_dependency_graph) get(name string) *type_graph_node { + return graph.all_types[name] +} + +func new_type_dependency_graph() *type_dependency_graph { + t := new(type_dependency_graph) + t.all_types = make(map[string]*type_graph_node) + return t +} + +func new_type_graph_node() *type_graph_node { + t := new(type_graph_node) + return t +} + +func (graph *type_dependency_graph) get_node_dependencies(t *type_graph_node) (nodes []*type_graph_node, err error) { + nodes = t.depends_on + return +} + +func (graph *type_dependency_graph) check_sub_dependency_cycle(root_node, sub_node *type_graph_node) (err error) { + var sub_node_deps []*type_graph_node + sub_node_deps, err = graph.get_node_dependencies(sub_node) + for _, dependency_node := range sub_node_deps { + if dependency_node == root_node { + return fmt.Errorf("cycle detected with %s", root_node.t.GetName()) + } + + // recursively check for deeper dependency cycles + if err = graph.check_sub_dependency_cycle(root_node, dependency_node); err != nil { + return + } + } + + return +} + +func (graph *type_dependency_graph) check_root_dependency_cycle(node *type_graph_node) (err error) { + var node_deps []*type_graph_node + node_deps, err = graph.get_node_dependencies(node) + for _, dependency_node := range node_deps { + // check for obvious self-referential + if dependency_node == node { + return fmt.Errorf("immediate %s->%s self-reference", node.t.GetName(), node.t.GetName()) + } + + // recursively check for deeper dependency cycles + if err = graph.check_sub_dependency_cycle(node, dependency_node); err != nil { + return + } + } + + return +} + +func (graph *type_dependency_graph) remove_edge(dependency, dependent *type_graph_node) (err error) { + dependent_index := slices.Index(dependent.depends_on, dependency) + if dependent_index == -1 { + err = fmt.Errorf("dependency %s not found in dependent %s", dependency.t.GetName(), dependent.t.GetName()) + return + } + + dependency_index := slices.Index(dependency.is_depended_on_by, dependent) + + if dependency_index == -1 { + err = fmt.Errorf("dependent %s not found in dependency %s", dependent.t.GetName(), dependency.t.GetName()) + return + } + + dependent.depends_on = slices.Delete(dependent.depends_on, dependent_index, dependent_index+1) + dependency.is_depended_on_by = slices.Delete(dependency.is_depended_on_by, dependency_index, dependency_index+1) + + return nil +} + +func (graph *type_dependency_graph) Load(types *Types) (err error) { + // combine all types into an array + var all_types []*type_graph_node + + type_exists := map[string]bool{} + + for _, alias_type := range types.Types { + t := new_type_graph_node() + at := alias_type + t.t = &at + + if !type_exists[t.String()] { + type_exists[t.String()] = true + all_types = append(all_types, t) + } + } + + for _, struct_type := range types.Structs { + t := new_type_graph_node() + st := struct_type + t.t = &st + + if !type_exists[t.String()] { + type_exists[t.String()] = true + all_types = append(all_types, t) + } + } + + for _, union_type := range types.Unions { + t := new_type_graph_node() + ut := union_type + t.t = &ut + + if !type_exists[t.String()] { + type_exists[t.String()] = true + all_types = append(all_types, t) + } + } + + for _, func_type := range types.Functions { + t := new_type_graph_node() + ft := func_type + t.t = &ft + + if !type_exists[t.String()] { + type_exists[t.String()] = true + all_types = append(all_types, t) + } + } + + // load types into map + graph.all_types = make(map[string]*type_graph_node) + for _, t := range all_types { + graph.all_types[t.t.GetName()] = t + } + + // build graph + for _, t := range all_types { + for _, dependency_name := range t.t.Dependencies() { + if is_type_name_builtin(dependency_name) { + continue + } + + dependency_type := graph.get(dependency_name) + if dependency_type == nil { + err = fmt.Errorf("unknown dependency name %s", dependency_name) + return + } + + if !slices.Contains(t.depends_on, dependency_type) { + t.depends_on = append(t.depends_on, dependency_type) + } + + if !slices.Contains(dependency_type.is_depended_on_by, t) { + dependency_type.is_depended_on_by = append(dependency_type.is_depended_on_by, t) + } + } + } + + // check for cycles + for _, t := range all_types { + if err = graph.check_root_dependency_cycle(t); err != nil { + return + } + } + + return +} + +type type_dependency_sorter struct { + graph *type_dependency_graph + type_names []string +} + +func new_type_dependency_sorter(graph *type_dependency_graph) *type_dependency_sorter { + sorter := new(type_dependency_sorter) + sorter.graph = graph + for k := range sorter.graph.all_types { + sorter.type_names = append(sorter.type_names, k) + } + sort.Strings(sorter.type_names) + return sorter +} + +func (sorter *type_dependency_sorter) sort() (sorted []*type_graph_node, err error) { + // first, peel off types with no dependencies. + var s []*type_graph_node + var l []*type_graph_node + + for _, type_name := range sorter.type_names { + node := sorter.graph.all_types[type_name] + // + if len(node.depends_on) == 0 { + s = append(s, node) + } + } + + for len(s) != 0 { + n := s[0] + s = s[1:] + + l = append(l, n) + + n_dependents := make([]*type_graph_node, len(n.is_depended_on_by)) + copy(n_dependents, n.is_depended_on_by) + for i := range n_dependents { + m := n_dependents[i] + + if err = sorter.graph.remove_edge(n, m); err != nil { + return + } + + if len(m.depends_on) == 0 { + s = append(s, m) + } + } + } + + sorted = l + return +} + +func (graph *type_dependency_graph) Save() (types *Types, err error) { + sorter := new_type_dependency_sorter(graph) + + var sorted []*type_graph_node + sorted, err = sorter.sort() + if err != nil { + return + } + types = new(Types) + + for _, t := range sorted { + switch xt := t.t.(type) { + case *AliasType: + types.Types = append(types.Types, *xt) + case *StructType: + types.Structs = append(types.Structs, *xt) + case *UnionType: + types.Unions = append(types.Unions, *xt) + case *FunctionType: + types.Functions = append(types.Functions, *xt) + default: + panic(t) + } + } + + return + +} diff --git a/go/x64dbg/type_node.go b/go/x64dbg/type_node.go new file mode 100644 index 0000000..2459702 --- /dev/null +++ b/go/x64dbg/type_node.go @@ -0,0 +1,51 @@ +package x64dbg + +import ( + "slices" + "strings" +) + +type type_graph_set []*type_graph_node + +type graph_type interface { + GetName() string + Dependencies() []string +} + +type type_graph_node struct { + t graph_type + depends_on type_graph_set + is_depended_on_by type_graph_set +} + +func (t *type_graph_node) String() string { + return t.t.GetName() +} + +var builtins = []string{ + "char", + "bool", + "long", + "short", + "long long", + "int", + "float", + "double", + "char*", + "void*", + "int8_t", + "int16_t", + "int32_t", + "int64_t", + "uint8_t", + "uint16_t", + "uint32_t", + "uint64_t", +} + +func is_type_name_builtin(t string) bool { + if strings.HasSuffix(t, "*") { + return true + } + return slices.Contains(builtins, t) +} diff --git a/go/x64dbg/types.go b/go/x64dbg/types.go index c591bd4..d474562 100644 --- a/go/x64dbg/types.go +++ b/go/x64dbg/types.go @@ -5,39 +5,9 @@ import ( "os" ) -type Type struct { - Type string `json:"type"` - Name string `json:"name"` - ArraySize int32 `json:"arrsize,omitempty"` -} - -type StructMemberType struct { - Type string `json:"type"` - Name string `json:"name"` - ArraySize int32 `json:"arrsize,omitempty"` - Offset int32 `json:"offset,omitempty"` -} - -type StructType struct { - Name string `json:"name"` - Members []StructMemberType `json:"members,omitempty"` -} - -type UnionType struct { - Name string `json:"name"` - Members []Type `json:"members,omitempty"` -} - -type FunctionType struct { - ReturnType string `json:"rettype"` - CallConvention string `json:"callconv"` - NoReturn bool `json:"noreturn"` - Name string `json:"name"` - Arguments []Type `json:"arguments,omitempty"` -} - +// Describes the format of an x64dbg type information file type Types struct { - Types []Type `json:"types,omitempty"` + Types []AliasType `json:"types,omitempty"` Structs []StructType `json:"structs,omitempty"` Unions []UnionType `json:"unions,omitempty"` Functions []FunctionType `json:"functions,omitempty"` @@ -59,3 +29,21 @@ func SaveTypes(name string, types *Types) (err error) { err = file.Close() return } + +func LoadTypes(name string) (types *Types, err error) { + var file *os.File + file, err = os.Open(name) + if err != nil { + return + } + + types = new(Types) + + e := json.NewDecoder(file) + if err = e.Decode(types); err != nil { + return + } + + err = file.Close() + return +} diff --git a/go/x64dbg/union_type.go b/go/x64dbg/union_type.go new file mode 100644 index 0000000..c970c54 --- /dev/null +++ b/go/x64dbg/union_type.go @@ -0,0 +1,18 @@ +package x64dbg + +type UnionType struct { + Name string `json:"name"` + Members []AliasType `json:"members,omitempty"` +} + +func (union *UnionType) GetName() string { + return union.Name +} + +func (union *UnionType) Dependencies() (s []string) { + for _, member := range union.Members { + s = append(s, member.Type) + } + + return +}