feat(profile): binana x64dbg-gen now generates a types file from C source headers

This commit is contained in:
phaneron 2024-07-17 01:37:26 -04:00
parent e3ec21ecec
commit 5d5630a6cb
19 changed files with 2961 additions and 354 deletions

View file

@ -38,7 +38,6 @@ func init() {
x64dbg_gen.Flags().StringP("game", "g", "3.3.5a", "the game profile")
x64dbg_gen.Flags().StringP("module-name", "m", "wow.exe", "the name of the module")
x64dbg_gen.Flags().StringP("base-address", "b", "00400000", "the base address of the module")
x64dbg_gen.Flags().BoolP("compress", "c", false, "enable/disable lz4 compression of the x64dbg database")
rootCmd.AddCommand(x64dbg_gen)
}

View file

@ -1,117 +1,50 @@
package cmd
import (
"fmt"
"os"
"path/filepath"
"strconv"
"github.com/spf13/cobra"
"github.com/thunderbrewhq/binana/go/symfile"
"github.com/thunderbrewhq/binana/go/x64dbg"
)
func hex_address(u uint64) string {
return fmt.Sprintf("0x%x", u)
}
// rootCmd represents the base command when called without any subcommands
var x64dbg_gen = &cobra.Command{
Use: "x64dbg-gen",
Short: "Generate x64dbg database using a symfile",
Run: x64dbg_gen_func,
}
func x64dbg_gen_func(cmd *cobra.Command, args []string) {
compress, err := cmd.Flags().GetBool("compress")
if err != nil {
panic(err)
}
module_name, err := cmd.Flags().GetString("module-name")
if err != nil {
panic(err)
}
base_address_s, err := cmd.Flags().GetString("base-address")
if err != nil {
panic(err)
}
base_address, err := strconv.ParseUint(base_address_s, 16, 64)
if err != nil {
panic(err)
}
game_profile, err := cmd.Flags().GetString("game")
if err != nil {
panic(err)
}
symfile_path := filepath.Join(game_profile, "symbol", "main.sym")
file, err := os.Open(symfile_path)
if err != nil {
fmt.Println(err)
os.Exit(1)
return
}
table := new(symfile.InMemoryTable)
if err := symfile.Load(table, file); err != nil {
fmt.Println(err)
os.Exit(1)
return
}
file.Close()
fmt.Printf("loaded %d symbols\n", table.Len())
var dd x64dbg.Database
for _, entry := range table.Entries {
relative_start_address := entry.StartAddress - base_address
relative_end_address := relative_start_address
if entry.EndAddress != 0 {
relative_end_address = entry.EndAddress - base_address
// for x64dbg, the end address is the last instruction.
// for us, the end address is the address immediately after the last instruction.
relative_end_address -= 1
}
if relative_end_address < relative_start_address || relative_end_address-relative_start_address >= 10000 {
fmt.Printf("Wtf symbol %s %08x %08x (offset %d)\n", entry.Name, relative_start_address, relative_end_address, relative_end_address-relative_start_address)
}
// create label
var label x64dbg.Label
label.Manual = true
label.Address = hex_address(relative_start_address)
label.Text = entry.Name
label.Module = module_name
dd.Labels = append(dd.Labels, label)
if entry.Kind == symfile.Function {
var fn x64dbg.Function
fn.Manual = true
fn.Start = hex_address(relative_start_address)
fn.End = hex_address(relative_end_address)
fn.Module = module_name
fn.InstructionCount = hex_address(0)
fn.Parent = hex_address(relative_start_address)
dd.Functions = append(dd.Functions, fn)
}
}
dd_path := filepath.Join(game_profile, "x32dbg", "game.dd32")
if err = x64dbg.SaveDatabase(dd_path, &dd, compress); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
package cmd
import (
"fmt"
"os"
"strconv"
"github.com/spf13/cobra"
"github.com/thunderbrewhq/binana/go/profile"
)
var x64dbg_gen = &cobra.Command{
Use: "x64dbg-gen",
Short: "Generate x64dbg database using a symfile",
Run: x64dbg_gen_func,
}
func x64dbg_gen_func(cmd *cobra.Command, args []string) {
// get command line arguments
module_name, err := cmd.Flags().GetString("module-name")
if err != nil {
panic(err)
}
base_address_s, err := cmd.Flags().GetString("base-address")
if err != nil {
panic(err)
}
// parse the base address of game EXE
base_address, err := strconv.ParseUint(base_address_s, 16, 64)
if err != nil {
panic(err)
}
game_profile_directory, err := cmd.Flags().GetString("game")
if err != nil {
panic(err)
}
game_profile, err := profile.Open(game_profile_directory)
if err != nil {
panic(err)
}
if err = game_profile.CreateX64dbgFiles(module_name, base_address); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

View file

@ -0,0 +1,6 @@
#ifndef SYSTEM_STD_BOOL_H
#define SYSTEM_STD_BOOL_H
typedef char bool;
#endif

View file

@ -0,0 +1,17 @@
#ifndef SYSTEM_STD_INT_H
#define SYSTEM_STD_INT_H
typedef signed char int8_t;
typedef short int16_t;
typedef int int32_t;
typedef long long int64_t;
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
typedef int32_t ptrdiff_t;
typedef uint32_t uintptr_t;
typedef int32_t intptr_t;
#endif

51
go/profile/open.go Normal file
View file

@ -0,0 +1,51 @@
package profile
import (
"fmt"
"io/fs"
"os"
"path/filepath"
"github.com/thunderbrewhq/binana/go/symfile"
)
type Profile struct {
Directory string
SymbolTable *symfile.InMemoryTable
}
func Open(profile_directory string) (profile *Profile, err error) {
var dir fs.FileInfo
dir, err = os.Stat(profile_directory)
if err != nil {
return
}
if !dir.IsDir() {
err = fmt.Errorf("profile: game profile is not a directory")
return
}
fmt.Println("Opening profile", profile_directory)
profile = new(Profile)
profile.Directory = profile_directory
path_to_symbols_file := filepath.Join(profile_directory, "symbol", "main.sym")
var symbols_file *os.File
symbols_file, err = os.Open(path_to_symbols_file)
if err != nil {
return
}
profile.SymbolTable = new(symfile.InMemoryTable)
if err = symfile.Load(profile.SymbolTable, symbols_file); err != nil {
return
}
symbols_file.Close()
//
return
}

13
go/profile/x64dbg.go Normal file
View file

@ -0,0 +1,13 @@
package profile
func (profile *Profile) CreateX64dbgFiles(module_name string, base_address uint64) (err error) {
if err = profile.generate_x64dbg_database(module_name, base_address); err != nil {
return
}
if err = profile.generate_x64dbg_types(); err != nil {
return
}
return
}

View file

@ -0,0 +1,66 @@
package profile
import (
"fmt"
"path/filepath"
"github.com/thunderbrewhq/binana/go/symfile"
"github.com/thunderbrewhq/binana/go/x64dbg"
)
func hex_address(u uint64) string {
return fmt.Sprintf("0x%x", u)
}
func (profile *Profile) generate_x64dbg_database(module_name string, base_address uint64) (err error) {
// Convert symbol table into x64dbg database
var dd x64dbg.Database
for _, entry := range profile.SymbolTable.Entries {
relative_start_address := entry.StartAddress - base_address
relative_end_address := relative_start_address
if entry.EndAddress != 0 {
relative_end_address = entry.EndAddress - base_address
// for x64dbg, the end address is the last instruction.
// for us, the end address is the address immediately after the last instruction.
relative_end_address -= 1
}
if relative_end_address < relative_start_address || relative_end_address-relative_start_address >= 10000 {
fmt.Printf("Strange symbol %s %08x %08x (offset %d)\n", entry.Name, relative_start_address, relative_end_address, relative_end_address-relative_start_address)
}
// create label
var label x64dbg.Label
label.Manual = true
label.Address = hex_address(relative_start_address)
label.Text = entry.Name
label.Module = module_name
dd.Labels = append(dd.Labels, label)
if entry.Kind == symfile.Function {
var fn x64dbg.Function
fn.Manual = true
fn.Start = hex_address(relative_start_address)
fn.End = hex_address(relative_end_address)
fn.Module = module_name
fn.InstructionCount = hex_address(0)
fn.Parent = hex_address(relative_start_address)
dd.Functions = append(dd.Functions, fn)
}
}
// save database
dd_path := filepath.Join(profile.Directory, "x32dbg", "game.dd32")
if err = x64dbg.SaveDatabase(dd_path, &dd); err != nil {
return
}
fmt.Println("database generated!", dd_path)
return
}

View file

@ -0,0 +1,236 @@
package profile
import (
"fmt"
"path/filepath"
"slices"
"sort"
"github.com/thunderbrewhq/binana/go/x64dbg"
"modernc.org/cc/v3"
)
func sort_string_ids(ids []cc.StringID) {
sort.Slice(ids, func(i, j int) bool {
return ids[i] < ids[j]
})
}
func cc_type_to_struct_member_type(t cc.Type) (m x64dbg.StructMemberType) {
var s string
var arrsize int32 = 1
var array bool
loop:
for t != nil {
if t.Name().String() != "" {
s = t.Name().String() + s
break
}
switch t.Kind() {
case cc.Ptr:
s = "*" + s
case cc.Array:
array = true
arrsize *= int32(t.Len())
case cc.Function:
s = "void*" + s
break loop
default:
s = t.Kind().String() + s
break loop
}
t = t.Elem()
}
if array {
m.ArraySize = arrsize
}
m.Offset = -1
m.Type = s
return
}
func cc_type_to_typedef(t cc.Type) (m x64dbg.Type) {
var s string
var arrsize int32 = 1
var array bool
loop:
for t != nil {
if t.Name().String() != "" && t.Kind() != cc.Enum {
s = t.Name().String() + s
break
}
switch t.Kind() {
case cc.Enum:
s = t.EnumType().String() + s
break loop
case cc.Ptr:
s = "*" + s
case cc.Array:
array = true
arrsize *= int32(t.Len())
case cc.Function:
s = "void*" + s
default:
s = t.Kind().String() + s
break loop
}
t = t.Elem()
}
if array {
m.ArraySize = arrsize
}
m.Type = s
return
}
// parses the C headers and generates a matching x64dbg types.json file
func (profile *Profile) generate_x64dbg_types() (err error) {
// parse C headers
var cc_config cc.Config
cc_config.ABI, err = cc.NewABI("windows", "386")
if err != nil {
panic(err)
}
cc_include_paths := []string{
filepath.Join(profile.Directory, "include"),
}
cc_system_include_paths := []string{
filepath.Join("go", "profile", "include"),
}
cc_sources := []cc.Source{
cc.Source{
Name: filepath.Join(profile.Directory, "include", "main.h"),
},
}
var ast *cc.AST
ast, err = cc.Translate(&cc_config, cc_include_paths, cc_system_include_paths, cc_sources)
if err != nil {
return
}
var x64_types x64dbg.Types
// Parse integer types
var scope_ids []cc.StringID
for name := range ast.Scope {
scope_ids = append(scope_ids, name)
}
sort_string_ids(scope_ids)
// these types needed by the C parser can't be included in x64dbg json
ignore_types := []string{
"int8_t",
"int16_t",
"int32_t",
"int64_t",
"uint8_t",
"uint16_t",
"uint32_t",
"uint64_t",
"bool",
}
for _, scope_id := range scope_ids {
scope := ast.Scope[scope_id]
for _, node := range scope {
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())
x64_type.Name = scope_id.String()
if !slices.Contains(ignore_types, x64_type.Name) {
x64_types.Types = append(x64_types.Types, x64_type)
}
}
}
}
}
}
var struct_names []cc.StringID
for struct_name := range ast.StructTypes {
struct_names = append(struct_names, struct_name)
}
sort.Slice(struct_names, func(i, j int) bool {
return struct_names[i] < struct_names[j]
})
for _, struct_name := range struct_names {
struct_type := ast.StructTypes[struct_name]
var x64_struct x64dbg.StructType
x64_struct.Name = struct_name.String()
for i := range struct_type.NumField() {
struct_member := struct_type.FieldByIndex([]int{i})
// If struct member is a union
if struct_member.Type().Kind() == cc.Union {
union_member_name := struct_member.Name().String()
if union_member_name == "" {
// union is anonymous
union_member_name = fmt.Sprintf("%04d", struct_member.Offset())
}
union_type_name := fmt.Sprintf("%s__%s", struct_name.String(), union_member_name)
var x64_union x64dbg.UnionType
x64_union.Name = union_type_name
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())
x64_union_member.Name = union_field.Name().String()
x64_union.Members = append(x64_union.Members, x64_union_member)
}
x64_types.Unions = append(x64_types.Unions, x64_union)
x64_struct_member := x64dbg.StructMemberType{}
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.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 = int32(struct_member.Offset())
x64_struct.Members = append(x64_struct.Members, x64_struct_member)
}
}
x64_types.Structs = append(x64_types.Structs, x64_struct)
}
types_file_path := filepath.Join(profile.Directory, "x32dbg", "types.json")
err = x64dbg.SaveTypes(types_file_path, &x64_types)
if err != nil {
return
}
fmt.Println("types generated!", types_file_path)
return
}

View file

@ -1,29 +1,33 @@
package symfile
import (
"slices"
"sort"
)
// Most tables are reasonably-sized and can be kept in memory
type InMemoryTable struct {
Entries []Entry
}
func (t *InMemoryTable) Insert(entry *Entry) (err error) {
i := sort.Search(len(t.Entries), func(i int) bool {
return t.Entries[i].StartAddress >= entry.StartAddress
})
if i < len(t.Entries) {
t.Entries = slices.Insert(t.Entries, i, *entry)
} else {
t.Entries = append(t.Entries, *entry)
}
return
}
func (t *InMemoryTable) Len() int {
return len(t.Entries)
}
package symfile
import (
"slices"
"sort"
)
// Most tables are reasonably-sized and can be kept in memory
type InMemoryTable struct {
Entries []Entry
}
func (t *InMemoryTable) Insert(entry *Entry) (err error) {
i := sort.Search(len(t.Entries), func(i int) bool {
return t.Entries[i].StartAddress >= entry.StartAddress
})
if i < len(t.Entries) {
t.Entries = slices.Insert(t.Entries, i, *entry)
} else {
t.Entries = append(t.Entries, *entry)
}
return
}
func (t *InMemoryTable) Len() int {
return len(t.Entries)
}
func NewInMemoryTable() *InMemoryTable {
return new(InMemoryTable)
}

View file

@ -1,44 +1,43 @@
package symfile
import (
"io"
)
// What kind of Entry is this?
type EntryKind uint8
const (
// Something that can be executed
Function EntryKind = 'f'
// Something that is read or written to
DataLabel EntryKind = 'l'
)
var (
valid_kinds = []EntryKind{Function, DataLabel}
)
// An entry in the table
type Entry struct {
// Undecorated, raw name
Name string
// Offset to the start of the function or data
StartAddress uint64
// What kind of Entry is this?
Kind EntryKind
// Any table entry can have a comment after a ';' column
Comment string
// Attributes
// end=AABBCCEEDD
EndAddress uint64
}
type Table interface {
Insert(entry *Entry) (err error)
// Find() (iter func() (entry *Entry, err error))
}
func Load(table Table, text io.Reader) (err error) {
err = load(text, table)
return
}
package symfile
import (
"io"
)
// What kind of Entry is this?
type EntryKind uint8
const (
// Something that can be executed
Function EntryKind = 'f'
// Something that is read or written to
DataLabel EntryKind = 'l'
)
var (
valid_kinds = []EntryKind{Function, DataLabel}
)
// An entry in the table
type Entry struct {
// Undecorated, raw name
Name string
// Offset to the start of the function or data
StartAddress uint64
// What kind of Entry is this?
Kind EntryKind
// Any table entry can have a comment after a ';' column
Comment string
// Attributes
// end=AABBCCEEDD
EndAddress uint64
}
type Table interface {
Insert(entry *Entry) (err error)
}
func Load(table Table, text io.Reader) (err error) {
err = load(text, table)
return
}

View file

@ -1,91 +1,77 @@
package x64dbg
import (
"encoding/json"
"io"
"os"
"github.com/pierrec/lz4/v4"
)
type Function struct {
Manual bool `json:"manual,omitempty"`
Start string `json:"start,omitempty"`
End string `json:"end,omitempty"`
Module string `json:"module,omitempty"`
InstructionCount string `json:"icount,omitempty"`
Parent string `json:"parent,omitempty"`
}
type Comment struct {
Manual bool `json:"manual,omitempty"`
Module string `json:"module,omitempty"`
Text string `json:"text,omitempty"`
Address string `json:"address,omitempty"`
}
type Label struct {
Manual bool `json:"manual,omitempty"`
Module string `json:"module,omitempty"`
Text string `json:"text,omitempty"`
Address string `json:"address,omitempty"`
}
type Bookmark struct {
Manual bool `json:"manual,omitempty"`
Module string `json:"module,omitempty"`
Address string `json:"address,omitempty"`
}
type Breakpoint struct {
Address string `json:"address,omitempty"`
CommandText string `json:"commandText,omitempty"`
Enabled bool `json:"enabled,omitempty"`
FastResume string `json:"fastResume,omitempty"`
OldBytes string `json:"oldbytes,omitempty"`
Type string `json:"type,omitempty"`
Module string `json:"module,omitempty"`
TitanType string `json:"titantype,omitempty"`
Name string `json:"name,omitempty"`
BreakCondition string `json:"breakCondition,omitempty"`
LogText string `json:"logText,omitempty"`
LogCondition string `json:"logCondition,omitempty"`
Silent string `json:"silent,omitempty"`
CommandCondition string `json:"commandCondition,omitempty"`
}
type Database struct {
Functions []Function `json:"functions,omitempty"`
Comments []Comment `json:"comments,omitempty"`
Labels []Label `json:"labels,omitempty"`
Bookmarks []Bookmark `json:"bookmarks,omitempty"`
Breakpoints []Breakpoint `json:"breakpoints,omitempty"`
}
func SaveDatabase(name string, database *Database, compress bool) (err error) {
var file *os.File
file, err = os.Create(name)
if err != nil {
return
}
var writecloser io.WriteCloser = file
if compress {
lz4_writer := lz4.NewWriter(file)
writecloser = lz4_writer
}
e := json.NewEncoder(writecloser)
e.SetIndent("", " ")
if err = e.Encode(database); err != nil {
return
}
if compress {
writecloser.Close()
}
err = file.Close()
return
}
package x64dbg
import (
"encoding/json"
"os"
)
type Function struct {
Manual bool `json:"manual,omitempty"`
Start string `json:"start,omitempty"`
End string `json:"end,omitempty"`
Module string `json:"module,omitempty"`
InstructionCount string `json:"icount,omitempty"`
Parent string `json:"parent,omitempty"`
}
type Comment struct {
Manual bool `json:"manual,omitempty"`
Module string `json:"module,omitempty"`
Text string `json:"text,omitempty"`
Address string `json:"address,omitempty"`
}
type Label struct {
Manual bool `json:"manual,omitempty"`
Module string `json:"module,omitempty"`
Text string `json:"text,omitempty"`
Address string `json:"address,omitempty"`
}
type Bookmark struct {
Manual bool `json:"manual,omitempty"`
Module string `json:"module,omitempty"`
Address string `json:"address,omitempty"`
}
type Breakpoint struct {
Address string `json:"address,omitempty"`
CommandText string `json:"commandText,omitempty"`
Enabled bool `json:"enabled,omitempty"`
FastResume string `json:"fastResume,omitempty"`
OldBytes string `json:"oldbytes,omitempty"`
Type string `json:"type,omitempty"`
Module string `json:"module,omitempty"`
TitanType string `json:"titantype,omitempty"`
Name string `json:"name,omitempty"`
BreakCondition string `json:"breakCondition,omitempty"`
LogText string `json:"logText,omitempty"`
LogCondition string `json:"logCondition,omitempty"`
Silent string `json:"silent,omitempty"`
CommandCondition string `json:"commandCondition,omitempty"`
}
type Database struct {
Functions []Function `json:"functions,omitempty"`
Comments []Comment `json:"comments,omitempty"`
Labels []Label `json:"labels,omitempty"`
Bookmarks []Bookmark `json:"bookmarks,omitempty"`
Breakpoints []Breakpoint `json:"breakpoints,omitempty"`
}
func SaveDatabase(name string, database *Database) (err error) {
var file *os.File
file, err = os.Create(name)
if err != nil {
return
}
e := json.NewEncoder(file)
e.SetIndent("", " ")
if err = e.Encode(database); err != nil {
return
}
err = file.Close()
return
}

61
go/x64dbg/types.go Normal file
View file

@ -0,0 +1,61 @@
package x64dbg
import (
"encoding/json"
"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"`
}
type Types struct {
Types []Type `json:"types,omitempty"`
Structs []StructType `json:"structs,omitempty"`
Unions []UnionType `json:"unions,omitempty"`
Functions []FunctionType `json:"functions,omitempty"`
}
func SaveTypes(name string, types *Types) (err error) {
var file *os.File
file, err = os.Create(name)
if err != nil {
return
}
e := json.NewEncoder(file)
e.SetIndent("", " ")
if err = e.Encode(types); err != nil {
return
}
err = file.Close()
return
}