chore(binana): update files

This commit is contained in:
phaneron 2024-07-13 17:42:21 -04:00
parent 1400de8b1f
commit c30e1199d7
24 changed files with 35645 additions and 4286 deletions

0
go/cmd/binana/LICENSE Normal file
View file

44
go/cmd/binana/cmd/root.go Normal file
View file

@ -0,0 +1,44 @@
/*
Copyright © 2024 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"os"
"github.com/spf13/cobra"
)
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "binana",
Short: "Binana helper tool",
}
// 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()
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.
// rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.binana.yaml)")
// 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")
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

@ -0,0 +1,117 @@
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)
}
}

7
go/cmd/binana/main.go Normal file
View file

@ -0,0 +1,7 @@
package main
import "github.com/thunderbrewhq/binana/go/cmd/binana/cmd"
func main() {
cmd.Execute()
}

139
go/symfile/loader.go Normal file
View file

@ -0,0 +1,139 @@
package symfile
import (
"bufio"
"errors"
"fmt"
"io"
"slices"
"strconv"
"strings"
)
const min_columns = 3
type loader struct {
input *bufio.Reader
table Table
line_number int
}
func (l *loader) read_line() (line string, err error) {
l.line_number++
line, err = l.input.ReadString('\n')
if err != nil {
return
}
line = strings.TrimRight(line, "\r\n")
return
}
func (l *loader) parse_line(line string) (err error) {
// trim extraneous whitespace
line = strings.Trim(line, " \t")
// split into columns
columns := strings.Split(line, " ")
// validate
if len(columns) < min_columns {
// this line is discarded but not in error
return
}
var (
start_address uint64
comment_text string
)
// get name of symbol
name_column := columns[0]
if name_column == "" {
return fmt.Errorf("symfile: (*loader).parse_line: line %d: entry has invalid name '%s", l.line_number, name_column)
}
start_address, err = strconv.ParseUint(columns[1], 16, 64)
if err != nil {
return
}
kind_column := columns[2]
if len(kind_column) != 1 {
return fmt.Errorf("symfile: (*loader).parse_line: line %d: entry has invalid kind", l.line_number)
}
kind := EntryKind(kind_column[0])
if !slices.Contains(valid_kinds, kind) {
return fmt.Errorf("symfile: (*loader).parse_line: line %d: entry has invalid kind", l.line_number)
}
// find index of comment column
index_of_comment := slices.Index(columns, ";")
var num_semantic_columns int
if index_of_comment != -1 {
num_semantic_columns = index_of_comment
comment_text_columns := columns[index_of_comment+1:]
comment_text = strings.Join(comment_text_columns, " ")
} else {
num_semantic_columns = len(columns)
}
// Start to build entry
var entry Entry
entry.Name = name_column
entry.StartAddress = start_address
entry.Kind = kind
entry.Comment = comment_text
// build attributes
if num_semantic_columns > 3 {
for _, column := range columns[3:] {
key, value, found := strings.Cut(column, "=")
if found {
switch key {
case "end":
entry.EndAddress, err = strconv.ParseUint(value, 16, 64)
if err != nil {
return
}
default:
return fmt.Errorf("symfile: (*loader).parse_line: line %d: unknown attribute '%s'", l.line_number, key)
}
}
}
}
err = l.table.Insert(&entry)
return
}
func load(text io.Reader, table Table) (err error) {
l := new(loader)
l.input = bufio.NewReader(text)
l.table = table
var (
line string
)
for {
line, err = l.read_line()
if err != nil {
if errors.Is(err, io.EOF) {
err = nil
break
} else {
return
}
}
if err = l.parse_line(line); err != nil {
return
}
}
return
}

View file

@ -0,0 +1,29 @@
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)
}

44
go/symfile/symfile.go Normal file
View file

@ -0,0 +1,44 @@
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
}

91
go/x64dbg/database.go Normal file
View file

@ -0,0 +1,91 @@
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
}