feat(go): x64dbg can't parse types correctly without being in the correct order, but we can use Kahn topological sorting to avoid issues

This commit is contained in:
phaneron 2024-08-01 01:19:29 -04:00
parent df04015c59
commit b6fb39c844
12 changed files with 475 additions and 35 deletions

2
go.sum
View file

@ -1,4 +1,6 @@
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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 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/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=

View file

@ -40,4 +40,5 @@ func init() {
x64dbg_gen.Flags().StringP("base-address", "b", "00400000", "the base address of the module") x64dbg_gen.Flags().StringP("base-address", "b", "00400000", "the base address of the module")
rootCmd.AddCommand(x64dbg_gen) rootCmd.AddCommand(x64dbg_gen)
rootCmd.AddCommand(x64dbg_typesort)
} }

View file

@ -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)
}
}

View file

@ -55,7 +55,7 @@ loop:
return 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 s string
var arrsize int32 = 1 var arrsize int32 = 1
var array bool var array bool
@ -156,7 +156,7 @@ func (profile *Profile) generate_x64dbg_types() (err error) {
if declarator, ok := node.(*cc.Declarator); ok { if declarator, ok := node.(*cc.Declarator); ok {
if declarator.IsTypedefName { if declarator.IsTypedefName {
if declarator.Type().Kind() != cc.Struct { 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() x64_type.Name = scope_id.String()
if !slices.Contains(ignore_types, x64_type.Name) { if !slices.Contains(ignore_types, x64_type.Name) {
x64_types.Types = append(x64_types.Types, x64_type) 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 var x64_struct x64dbg.StructType
x64_struct.Name = struct_name.String() x64_struct.Name = struct_name.String()
x64_struct.Size = int32(struct_type.Size())
for i := range struct_type.NumField() { for i := range struct_type.NumField() {
struct_member := struct_type.FieldByIndex([]int{i}) 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() { for i := range struct_member.Type().NumField() {
union_field := struct_member.Type().FieldByIndex([]int{i}) 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_member.Name = union_field.Name().String()
x64_union.Members = append(x64_union.Members, x64_union_member) 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.Type = union_type_name
x64_struct_member.Name = struct_member.Name().String() x64_struct_member.Name = struct_member.Name().String()
x64_struct_member.Offset = int32(struct_member.Offset()) x64_struct_member.Offset = int32(struct_member.Offset())
// x64_struct_member.Offset = -1
x64_struct.Members = append(x64_struct.Members, x64_struct_member) x64_struct.Members = append(x64_struct.Members, x64_struct_member)
} else { } else {
x64_struct_member := cc_type_to_struct_member_type(struct_member.Type()) x64_struct_member := cc_type_to_struct_member_type(struct_member.Type())
x64_struct_member.Name = struct_member.Name().String() x64_struct_member.Name = struct_member.Name().String()
// x64_struct_member.Offset = -1
x64_struct_member.Offset = int32(struct_member.Offset()) x64_struct_member.Offset = int32(struct_member.Offset())
x64_struct.Members = append(x64_struct.Members, x64_struct_member) 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") 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) err = x64dbg.SaveTypes(types_file_path, &x64_types)
if err != nil { if err != nil {
return return

17
go/x64dbg/alias_type.go Normal file
View file

@ -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,
}
}

View file

@ -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
}

17
go/x64dbg/sort_types.go Normal file
View file

@ -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
}

26
go/x64dbg/struct_type.go Normal file
View file

@ -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
}

View file

@ -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
}

51
go/x64dbg/type_node.go Normal file
View file

@ -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)
}

View file

@ -5,39 +5,9 @@ import (
"os" "os"
) )
type Type struct { // Describes the format of an x64dbg type information file
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"`
}
type Types struct { type Types struct {
Types []Type `json:"types,omitempty"` Types []AliasType `json:"types,omitempty"`
Structs []StructType `json:"structs,omitempty"` Structs []StructType `json:"structs,omitempty"`
Unions []UnionType `json:"unions,omitempty"` Unions []UnionType `json:"unions,omitempty"`
Functions []FunctionType `json:"functions,omitempty"` Functions []FunctionType `json:"functions,omitempty"`
@ -59,3 +29,21 @@ func SaveTypes(name string, types *Types) (err error) {
err = file.Close() err = file.Close()
return 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
}

18
go/x64dbg/union_type.go Normal file
View file

@ -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
}