-
Notifications
You must be signed in to change notification settings - Fork 4
/
main.go
487 lines (377 loc) · 12.9 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
package main
import (
"flag"
"fmt"
"math/rand"
"os"
"runtime"
"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("Memory 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 := strings.TrimSpace(r2cmd(r2p, "?v theend"))
// fixme: on Windows, we sometimes get output *from other calls to r2*
if status == "0x1" {
logrus.Warnf("[!] Bot %d has died", botid)
return true
}
if status != "0x0" {
logrus.Warnf("[!] Got invalid status '%s' for bot %d", status, botid)
}
return false
}
// The Windows terminal doesn't render ANSI escape codes by default,
// but at least, it supports opting in since Windows 10 1709.
// We could use windigo to call kernel32.SetConsoleMode, but this still
// wouldn't be enough as logrus seems to escape them. But if we force logrus to
// use color, it seems to call this function for us. :)
// see https://learn.microsoft.com/en-us/windows/console/setconsolemode
// see https://superuser.com/a/1300251/329759
func forceColor() {
logrus.Infof("Running on Windows; forcing color")
// make logrus use color
logrus.SetFormatter(&logrus.TextFormatter{
ForceColors: true,
FullTimestamp: false,
})
}
func main() {
if runtime.GOOS == "windows" {
forceColor()
}
fmt.Println("\x1b[33mhi\x1b[0m")
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()
}