about summary refs log tree commit diff
path: root/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'main.go')
-rw-r--r--main.go463
1 files changed, 463 insertions, 0 deletions
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..abaf5fa
--- /dev/null
+++ b/main.go
@@ -0,0 +1,463 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"math/rand"
+	"os"
+	"strings"
+	"time"
+
+	"github.com/radareorg/r2pipe-go"
+	"github.com/sirupsen/logrus"
+)
+
+// Config defines the meta config
+type Config struct {
+
+	// Arch defines the architecture the battle should run in
+	Arch string
+
+	// Bits defines the bitness
+	Bits int
+
+	// Memsize defines the arena size
+	Memsize int
+
+	// MaxProgSize defines the maximal bot size
+	MaxProgSize int
+
+	// Bots defines a list of bots to take part in the battle
+	Bots []Bot
+
+	// AmountOfBots defines the amount of bots taking part in the tournament
+	AmountOfBots int
+
+	// RandomOffsets defines the offset in memory where the bots should be placed
+	RandomOffsets []int
+
+	// GameRoundTime defines the length of a gameround
+	GameRoundDuration time.Duration
+}
+
+// Bot defines a bot
+type Bot struct {
+
+	// Path defines the path to the source of the bot
+	Path string
+
+	// Source defines the source of the bot after being compiled with rasm2
+	Source string
+
+	// Addr defines the initial address the bot is placed at
+	Addr int
+
+	// Regs defines the state of the registers of the bot
+	// It is used to store the registers after each round and restore them in the
+	// next round when the bot's turn has come
+	Regs string
+}
+
+func parseConfig() Config {
+	arch := flag.String("arch", "x86", "bot architecture (mips|arm|x86)")
+	bits := flag.Int("bits", 32, "bot bitness (8|16|32|64)")
+	maxProgSize := flag.Int("maxProgSize", 64, "the maximum bot size")
+	memPerBot := flag.Int("memPerBot", 512, "the amount of memory each bot should add to the arena")
+	gameRoundDuration := flag.Duration("t", 250*time.Millisecond, "The duration of a round")
+
+	v := flag.Bool("v", false, "info")
+	vv := flag.Bool("vv", false, "debug")
+	vvv := flag.Bool("vvv", false, "trace")
+
+	flag.Parse()
+
+	if *v == true {
+		logrus.SetLevel(logrus.InfoLevel)
+	} else if *vv == true {
+		logrus.SetLevel(logrus.DebugLevel)
+	} else if *vvv == true {
+		logrus.SetLevel(logrus.TraceLevel)
+	} else {
+		logrus.SetLevel(logrus.WarnLevel)
+	}
+
+	// parse all trailing command line arguments as path to bot sourcecode
+	amountOfBots := flag.NArg()
+
+	memsize := amountOfBots * *memPerBot
+
+	logrus.WithFields(logrus.Fields{
+		"mem per bot":  *memPerBot,
+		"amountOfBots": amountOfBots,
+		"memsize":      memsize,
+	}).Infof("Loaded config")
+
+	// define a config to return
+	config := Config{
+		Arch:              *arch,
+		Bits:              *bits,
+		Memsize:           memsize,
+		MaxProgSize:       *maxProgSize,
+		AmountOfBots:      amountOfBots,
+		GameRoundDuration: *gameRoundDuration,
+	}
+
+	return config
+}
+
+// define bots defines the bots given via command line arguments
+func defineBots(config *Config) {
+
+	logrus.Info("Defining the bots")
+
+	// define a list of bots by parsing the command line arguments one by one
+	var bots []Bot
+	for i := 0; i < config.AmountOfBots; i++ {
+		bot := Bot{
+			Path: flag.Arg(i),
+		}
+		bots = append(bots, bot)
+	}
+
+	config.Bots = bots
+}
+
+func r2cmd(r2p *r2pipe.Pipe, input string) string {
+
+	logrus.Tracef("> %s", input)
+
+	// send a command
+	buf1, err := r2p.Cmd(input)
+	if err != nil {
+		panic(err)
+	}
+
+	// return the result of the command as a string
+	return buf1
+}
+
+func buildBots(config *Config) {
+
+	logrus.Info("Building all bots")
+
+	// build all the bots
+	for i := 0; i < config.AmountOfBots; i++ {
+		buildBot(i, config)
+	}
+}
+
+// buildBot builds the bot located at the given path.
+func buildBot(i int, config *Config) {
+
+	logrus.Debugf("Building bot %d", i)
+
+	// open radare without input for building the bot
+	r2p1, err := r2pipe.NewPipe("--")
+	if err != nil {
+		panic(err)
+	}
+	defer r2p1.Close()
+
+	// Compile a warrior using rasm2
+	//
+	// This uses the given architecture, the given bitness and the given path in
+	// rasm2 to compile the bot
+	botPath := config.Bots[i].Path
+	radareCommand := fmt.Sprintf("rasm2 -a %s -b %d -f %s", config.Arch, config.Bits, botPath)
+	botSource := r2cmd(r2p1, radareCommand)
+
+	config.Bots[i].Source = botSource
+}
+
+// init initializes the arena
+func initArena(config *Config) *r2pipe.Pipe {
+
+	logrus.Info("Initializing the arena")
+	logrus.Debugf("Allocating %d bytes of memory...", config.Memsize)
+
+	// allocate memory
+	r2p, err := r2pipe.NewPipe(fmt.Sprintf("malloc://%d", config.Memsize))
+	if err != nil {
+		panic(err)
+	}
+
+	logrus.Info("Memoy successfully allocated")
+
+	// define the architecture and the bitness
+	_ = r2cmd(r2p, fmt.Sprintf("e asm.arch = %s", config.Arch))
+	_ = r2cmd(r2p, fmt.Sprintf("e asm.bits = %d", config.Bits))
+
+	// enable colors
+	// _ = r2cmd(r2p, "e scr.color = 0")
+	_ = r2cmd(r2p, "e scr.color = 3")
+	_ = r2cmd(r2p, "e scr.color.args = true")
+	_ = r2cmd(r2p, "e scr.color.bytes = true")
+	_ = r2cmd(r2p, "e scr.color.grep = true")
+	_ = r2cmd(r2p, "e scr.color.ops = true")
+	_ = r2cmd(r2p, "e scr.bgfill = true")
+	_ = r2cmd(r2p, "e scr.color.pipe = true")
+	_ = r2cmd(r2p, "e scr.utf8 = true")
+
+	// hex column width
+	_ = r2cmd(r2p, "e hex.cols = 32")
+
+	// initialize ESIL VM state
+	logrus.Debug("Initializing the ESIL VM")
+	_ = r2cmd(r2p, "aei")
+
+	// initialize ESIL VM stack
+	logrus.Debug("Initializing the ESIL Stack")
+	_ = r2cmd(r2p, "aeim")
+
+	// return the pipe
+	return r2p
+}
+
+// genRandomOffsets returns random offsets for all bots
+// This is used to get the offset the bots are initially placed in
+func genRandomOffsets(config *Config) {
+
+	logrus.Info("Generating random bot offsets")
+
+	// define the amount of bots, an array to store the offsets in and a counter
+	// to store the amount of tries it took to find a random positon for the bots
+	var amountOfBots int = len(config.Bots)
+	var offsets []int
+	var roundCounter int = 0
+
+	// seed the random number generator
+	rand.Seed(time.Now().UTC().UnixNano())
+
+	for {
+
+		// reset the offsets array
+		offsets = []int{}
+
+		// define a random address
+		// | ------------------------------------- | ----- |
+		// | generate an address in this space
+		address := rand.Intn(config.Memsize - config.MaxProgSize)
+		logrus.Tracef("%d", address)
+
+		// for all bots, try to generate another random address after the intially
+		// generated address and test if it fits in memory
+		for i := 0; i < amountOfBots; i++ {
+
+			// append the address to the offsets array
+			offsets = append(offsets, address)
+
+			// define a min and max bound
+			//
+			// | ------|-|----------------------------------|-|
+			// |       | |           in this space          | |
+			// a       b c                                  d e
+			//
+			// a = 0x0
+			// b = address
+			// c = address + config.MaxProcSize (min)
+			// d = config.Memsize - config.MaxProgSize (max)
+			// e = config.Memsize
+			min := address + config.MaxProgSize
+			max := config.Memsize - config.MaxProgSize
+
+			// if the new minimum bound is bigger or equal to the maximum bound,
+			// discard this try and start with a fresh new initial address
+			if min >= max {
+				roundCounter++
+				break
+			}
+
+			// generate a new address in the [min, max) range defined above
+			address = rand.Intn(max-min) + min
+			logrus.Tracef("%d", address)
+
+			// If there isn't enough space inbetween the address and the biggest
+			// possible address, as in, the biggest possible bot can't fit in that
+			// space, discard and start with a new fresh initial address
+			if (config.Memsize-config.MaxProgSize)-address < config.MaxProgSize {
+				roundCounter++
+				break
+			}
+		}
+
+		// if the needed amount of offsets has been found, break out of the infinite loop
+		if len(offsets) == amountOfBots {
+			break
+		}
+	}
+
+	logrus.Infof("Initial bot positions found after %d tries", roundCounter)
+
+	// debug print all offsets
+	var fields0 logrus.Fields = make(logrus.Fields)
+	for i := 0; i < len(offsets); i++ {
+		fields0[fmt.Sprintf("%d", i)] = offsets[i]
+	}
+	logrus.WithFields(fields0).Debug("Offsets")
+
+	// shuffle the offsets
+	rand.Shuffle(len(offsets), func(i, j int) {
+		offsets[i], offsets[j] = offsets[j], offsets[i]
+	})
+
+	// debug print the shuffled offsets
+	var fields1 logrus.Fields = make(logrus.Fields)
+	for i := 0; i < len(offsets); i++ {
+		fields1[fmt.Sprintf("%d", i)] = offsets[i]
+	}
+	logrus.WithFields(fields1).Debug("Shuffled offsets")
+
+	config.RandomOffsets = offsets
+}
+
+// place the bot in the arena at the given address
+func placeBot(r2p *r2pipe.Pipe, bot Bot, address int) {
+	_ = r2cmd(r2p, fmt.Sprintf("wx %s @ %d", bot.Source, address))
+}
+
+func placeBots(r2p *r2pipe.Pipe, config *Config) {
+
+	logrus.Info("Placing the bots in the arena")
+
+	// place each bot in the arena
+	for bot := 0; bot < len(config.Bots); bot++ {
+
+		// get the address where the bot should be placed
+		address := config.RandomOffsets[bot]
+
+		// Place the bot in the arena
+		logrus.Debugf("[i] Placing bot %d at %d", bot, address)
+		placeBot(r2p, config.Bots[bot], address)
+
+		logrus.Debugf("\n%s", r2cmd(r2p, fmt.Sprintf("pd 0x8 @ %d", address)))
+
+		// store the initial address of the bot in the according struct field
+		config.Bots[bot].Addr = address
+
+		// define the instruction point and the stack pointer
+		_ = r2cmd(r2p, fmt.Sprintf("aer PC=%d", config.Bots[bot].Addr))
+		_ = r2cmd(r2p, fmt.Sprintf("aer SP=SP+%d", config.Bots[bot].Addr))
+
+		// dump the registers of the bot for being able to switch inbetween them
+		// This is done in order to be able to play one step of each bot at a time,
+		// but sort of in parallel
+		initialRegisers := strings.Replace(r2cmd(r2p, "aerR"), "\n", ";", -1)
+		config.Bots[bot].Regs = initialRegisers
+	}
+}
+
+func defineErrors(r2p *r2pipe.Pipe) {
+	// handle errors in esil
+	_ = r2cmd(r2p, "e cmd.esil.todo=f theend=1")
+	_ = r2cmd(r2p, "e cmd.esil.trap=f theend=1")
+	_ = r2cmd(r2p, "e cmd.esil.intr=f theend=1")
+	_ = r2cmd(r2p, "e cmd.esil.ioer=f theend=1")
+	_ = r2cmd(r2p, "f theend=0")
+}
+
+// StepIn steps in and stores the state of the registers for the given bot
+func stepIn(r2p *r2pipe.Pipe) {
+	_ = r2cmd(r2p, "aes")
+}
+
+// switchPlayer returns the id of the next Player
+func switchPlayer(r2p *r2pipe.Pipe, currentPlayer int, config *Config) int {
+
+	// calculate the index of the nextPlayer
+	nextPlayer := (currentPlayer + 1) % config.AmountOfBots
+
+	return nextPlayer
+}
+
+func arena(r2p *r2pipe.Pipe, config *Config, id, gen int) string {
+	var res string = ""
+
+	// clear the screen
+	res += "\x1b[2J\x1b[0;0H"
+	// res += fmt.Sprintf("%s\n", r2cmd(r2p, "?eg 0 0"))
+
+	// print some general information such as the current user and the round the
+	// game is in
+	ip := fmt.Sprintf("%s\n", r2cmd(r2p, "aer~eip"))
+	res += fmt.Sprintf("Round: %d \t\t User: %d \t\t ip: %s\n", gen, id, ip)
+
+	// print the memory space
+	res += fmt.Sprintf("%s\n", r2cmd(r2p, "pxa 0x400 @ 0"))
+	// res += fmt.Sprintf("%s\n", r2cmd(r2p, fmt.Sprintf("pd 0x10 @ %d", config.Bots[id].Addr)))
+
+	// res += fmt.Sprintf("%s\n", r2cmd(r2p, "prc 0x200 @ 0"))
+
+	return res
+}
+
+// runGame actually runs the game (surprise!)
+func runGame(r2p *r2pipe.Pipe, config *Config) {
+
+	// start the competition
+	var botid int = 0
+	var round int = 0
+	for true {
+
+		// load the registers
+		r2cmd(r2p, config.Bots[botid].Regs)
+
+		// Step
+		stepIn(r2p)
+
+		// store the regisers
+		registers := r2cmd(r2p, "aerR")
+		registersStripped := strings.Replace(registers, "\n", ";", -1)
+		config.Bots[botid].Regs = registersStripped
+
+		logrus.Info(arena(r2p, config, botid, round))
+
+		if dead(r2p, botid) == true {
+			logrus.Warnf("DEAD (round %d)", round)
+			os.Exit(1)
+		}
+
+		// switch players, if the new botid is 0, a new round has begun
+		botid = switchPlayer(r2p, botid, config)
+		if botid == 0 {
+			round++
+		}
+
+		// sleep only a partial of the total round time, as a round is made up of
+		// the movements of multiple bots
+		time.Sleep(config.GameRoundDuration / time.Duration(config.AmountOfBots))
+	}
+}
+
+func dead(r2p *r2pipe.Pipe, botid int) bool {
+	status := r2cmd(r2p, "?v 1+theend")
+
+	if status != "" && status != "0x1" {
+		logrus.Warn("[!] Bot %d has died", botid)
+		return true
+	}
+	return false
+}
+
+func main() {
+	fmt.Println("hi")
+
+	config := parseConfig()
+	defineBots(&config)
+	buildBots(&config)
+	genRandomOffsets(&config)
+
+	// initialize the arena (allocate memory + initialize the ESIL VM & stack)
+	r2p := initArena(&config)
+
+	// place the bots in the arena
+	placeBots(r2p, &config)
+
+	// if an error occurs (interrupt, ioerror, trap, ...), the ESIL VM should set
+	// a flag that can be used to determine if a player has died
+	defineErrors(r2p)
+
+	// run the actual game
+	runGame(r2p, &config)
+
+	r2p.Close()
+}