From 1a57267a17c2fc17fb6e104846fabc3e363c326c Mon Sep 17 00:00:00 2001 From: Emile Date: Fri, 16 Aug 2024 19:50:26 +0200 Subject: initial commit --- src/bot.go | 842 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 842 insertions(+) create mode 100644 src/bot.go (limited to 'src/bot.go') diff --git a/src/bot.go b/src/bot.go new file mode 100644 index 0000000..1a0d342 --- /dev/null +++ b/src/bot.go @@ -0,0 +1,842 @@ +package main + +import ( + "fmt" + "html/template" + "log" + "net/http" + "strconv" + "strings" + "time" + + "github.com/gorilla/mux" + "github.com/radareorg/r2pipe-go" +) + +type Bot struct { + ID int + Name string + Source string + Users []User + + Archs []Arch + Bits []Bit +} + +////////////////////////////////////////////////////////////////////////////// +// GENERAL PURPOSE + +func BotCreate(name string, source string) (int, error) { + return globalState.InsertBot(Bot{Name: name, Source: source}) +} + +func BotUpdate(botid int, name string, source string) error { + return globalState.UpdateBot(Bot{ID: botid, Name: name, Source: source}) +} + +func BotGetById(id int) (Bot, error) { + return globalState.GetBotById(id) +} + +func BotGetAll() ([]Bot, error) { + return globalState.GetAllBot() +} + +func BotLinkArchIDs(botid int, archIDs []int) error { + return globalState.LinkArchIDsToBot(botid, archIDs) +} + +func BotLinkBitIDs(botid int, bitIDs []int) error { + return globalState.LinkBitIDsToBot(botid, bitIDs) +} + +////////////////////////////////////////////////////////////////////////////// +// DATABASE + +func (s *State) InsertBot(bot Bot) (int, error) { + res, err := s.db.Exec("INSERT INTO bots VALUES(NULL,?,?,?);", time.Now(), bot.Name, bot.Source) + if err != nil { + return 0, err + } + + var id int64 + if id, err = res.LastInsertId(); err != nil { + return 0, err + } + return int(id), nil +} + +func (s *State) UpdateBot(bot Bot) error { + _, err := s.db.Exec("UPDATE bots SET name=?, source=? WHERE id=?", bot.Name, bot.Source, bot.ID) + if err != nil { + return err + } + return nil +} + +func (s *State) GetBotById(id int) (Bot, error) { + var botid int + var botname string + var botsource string + + var ownerids string + var ownernames string + + var archids string + var archnames string + + var bitids string + var bitnames string + + err := s.db.QueryRow(` + SELECT + bo.id, bo.name, bo.source, + COALESCE(group_concat(ub.user_id), ""), + COALESCE(group_concat(us.name), ""), + COALESCE(group_concat(ab.arch_id), ""), + COALESCE(group_concat(ar.name), ""), + COALESCE(group_concat(bb.bit_id), ""), + COALESCE(group_concat(bi.name), "") + FROM bots bo + + LEFT JOIN user_bot_rel ub ON ub.bot_id = bo.id + LEFT JOIN users us ON us.id = ub.user_id + + LEFT JOIN arch_bot_rel ab ON ab.bot_id = bo.id + LEFT JOIN archs ar ON ar.id = ab.arch_id + + LEFT JOIN bit_bot_rel bb ON bb.bot_id = bo.id + LEFT JOIN bits bi ON bi.id = bb.bit_id + + WHERE bo.id=? + GROUP BY bo.id; + `, id).Scan(&botid, &botname, &botsource, &ownerids, &ownernames, &archids, &archnames, &bitids, &bitnames) + if err != nil { + log.Println(err) + return Bot{}, err + } + + // log.Println("botid: ", botid) + // log.Println("botname: ", botname) + // log.Println("botsource: ", botsource) + // log.Println("ownerids: ", ownerids) + // log.Println("ownernames: ", ownernames) + // log.Println("archid: ", archids) + // log.Println("archname: ", archnames) + // log.Println("bitid: ", bitids) + // log.Println("bitname: ", bitnames) + + ownerIDList := strings.Split(ownerids, ",") + ownerNameList := strings.Split(ownernames, ",") + + var users []User + for i, _ := range ownerIDList { + id, err := strconv.Atoi(ownerIDList[i]) + if err != nil { + log.Println("ERR1: ", err) + return Bot{}, err + } + users = append(users, User{ID: id, Name: ownerNameList[i], PasswordHash: nil}) + } + + // assemble the archs + archIDList := strings.Split(archids, ",") + archNameList := strings.Split(archnames, ",") + + var archs []Arch + if archIDList[0] != "" { + for i, _ := range archIDList { + id, err := strconv.Atoi(archIDList[i]) + if err != nil { + log.Println("Err handling archs: ", err) + return Bot{}, err + } + archs = append(archs, Arch{id, archNameList[i], true}) + } + } else { + archs = []Arch{} + } + + // assemble the bits + bitIDList := strings.Split(bitids, ",") + bitNameList := strings.Split(bitnames, ",") + + var bits []Bit + if bitIDList[0] != "" { + for i, _ := range bitIDList { + id, err := strconv.Atoi(bitIDList[i]) + if err != nil { + log.Println("Err handling bits: ", err) + return Bot{}, err + } + bits = append(bits, Bit{id, bitNameList[i], true}) + } + } else { + bits = []Bit{} + } + + switch { + case err != nil: + log.Println("ERR4: ", err) + return Bot{}, err + default: + return Bot{botid, botname, botsource, users, archs, bits}, nil + } +} + +func (s *State) UpdateBotSource(name string, source string) error { + _, err := s.db.Exec("UPDATE bots SET source=? WHERE name=?", source, name) + if err != nil { + return err + } else { + return nil + } +} + +func (s *State) UpdateBotName(orig_name string, new_name string) error { + _, err := s.db.Exec("UPDATE bots SET name=? WHERE name=?", new_name, orig_name) + if err != nil { + return err + } else { + return nil + } +} + +// Returns the users belonging to the given bot +func (s *State) GetBotUsers(botid int) ([]User, error) { + rows, err := s.db.Query("SELECT id, name FROM users u LEFT JOIN user_bot_rel ub ON ub.user_id = u.id WHERE ub.bot_id=?", botid) + defer rows.Close() + if err != nil { + return nil, err + } + + var users []User + for rows.Next() { + var user User + if err := rows.Scan(&user.ID, &user.Name); err != nil { + return users, err + } + users = append(users, user) + } + if err = rows.Err(); err != nil { + return users, err + } + return users, nil +} + +// Returns the users belonging to the given bot +func (s *State) GetAllBot() ([]Bot, error) { + rows, err := s.db.Query("SELECT id, name FROM bots;") + defer rows.Close() + if err != nil { + return nil, err + } + + var bots []Bot + for rows.Next() { + var bot Bot + if err := rows.Scan(&bot.ID, &bot.Name); err != nil { + return bots, err + } + bots = append(bots, bot) + } + if err = rows.Err(); err != nil { + return bots, err + } + return bots, nil +} + +// Returns the users belonging to the given bot +func (s *State) GetAllBotsWithUsers() ([]Bot, error) { + rows, err := s.db.Query(`SELECT + b.id, b.name, b.source, group_concat(ub.user_id), group_concat(u.name) + FROM bots b + LEFT JOIN user_bot_rel ub ON ub.bot_id = b.id + LEFT JOIN users u ON ub.user_id = u.id + GROUP BY b.id;`) + defer rows.Close() + if err != nil { + return nil, err + } + + var bots []Bot + for rows.Next() { + var bot Bot + var userIDListString string + var usernameListString string + + err := rows.Scan(&bot.ID, &bot.Name, &bot.Source, &userIDListString, &usernameListString) + if err != nil { + return nil, err + } + + userIDList := strings.Split(userIDListString, ",") + usernameList := strings.Split(usernameListString, ",") + + var users []User + for i, _ := range userIDList { + id, err := strconv.Atoi(userIDList[i]) + if err != nil { + return nil, err + } + users = append(users, User{ID: id, Name: usernameList[i], PasswordHash: nil}) + } + bot.Users = users + + bots = append(bots, bot) + } + if err = rows.Err(); err != nil { + return bots, err + } + return bots, nil +} + +func (s *State) LinkArchIDsToBot(botid int, archIDs []int) error { + // delete preexisting links + _, err := s.db.Exec("DELETE FROM arch_bot_rel WHERE bot_id=?;", botid) + if err != nil { + log.Println("Error deleting old arch bot link: ", err) + return err + } + + // yes, we're building this by hand, but as we only insert int's I'm just confident that whoever + // gets some sqli here just deserves it :D + query := "INSERT INTO arch_bot_rel (arch_id, bot_id) VALUES" + for idx, id := range archIDs { + query += fmt.Sprintf("(%d, %d)", id, botid) + if idx != len(archIDs)-1 { + query += ", " + } + } + query += ";" + log.Println(query) + + _, err = s.db.Exec(query) + if err != nil { + log.Println("LinkArchIDsToBot err: ", err) + return err + } else { + return nil + } +} + +func (s *State) LinkBitIDsToBot(botid int, bitIDs []int) error { + // delete preexisting links + _, err := s.db.Exec("DELETE FROM bit_bot_rel WHERE bot_id=?;", botid) + if err != nil { + log.Println("Error deleting old bit bot link: ", err) + return err + } + + // yes, we're building this by hand, but as we only insert int's I'm just confident that whoever + // gets some sqli here just deserves it :D + query := "INSERT INTO bit_bot_rel (bit_id, bot_id) VALUES" + for idx, id := range bitIDs { + query += fmt.Sprintf("(%d, %d)", id, botid) + if idx != len(bitIDs)-1 { + query += ", " + } + } + query += ";" + log.Println(query) + + _, err = s.db.Exec(query) + if err != nil { + log.Println("LinkBitIDsToBot err: ", err) + return err + } else { + return nil + } +} + +////////////////////////////////////////////////////////////////////////////// +// HTTP + +func botsHandler(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "GET": + // define data + data := map[string]interface{}{} + + session, _ := globalState.sessions.Get(r, "session") + username := session.Values["username"] + data["pagelink1"] = Link{"bot", "/bot"} + data["pagelink1options"] = []Link{ + {Name: "user", Target: "/user"}, + {Name: "battle", Target: "/battle"}, + } + data["pagelinknext"] = []Link{ + {Name: "new", Target: "/new"}, + } + + if username == nil { + http.Redirect(w, r, "/login", http.StatusMethodNotAllowed) + } + + user, err := UserGetUserFromUsername(username.(string)) + if err != nil { + data["err"] = "Could not fetch the user" + } else { + data["user"] = user + } + + bots, err := globalState.GetAllBotsWithUsers() + data["bots"] = bots + + // get the template + t, err := template.ParseGlob("./templates/*.html") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("500 - Error reading template file")) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // exec! + t.ExecuteTemplate(w, "bots", data) + default: + http.Redirect(w, r, "/", http.StatusMethodNotAllowed) + } +} + +func botSingleHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + botid, err := strconv.Atoi(vars["id"]) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("500 - Invalid bot id")) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + switch r.Method { + case "GET": + // define data + data := map[string]interface{}{} + data["pagelink1"] = Link{"bot", "/bot"} + data["pagelink1options"] = []Link{ + {Name: "user", Target: "/user"}, + {Name: "battle", Target: "/battle"}, + } + + // display errors passed via query parameters + log.Println("[d] Getting previous results") + queryres := r.URL.Query().Get("res") + if queryres != "" { + data["res"] = queryres + } + + session, _ := globalState.sessions.Get(r, "session") + username := session.Values["username"].(string) + + viewer, err := UserGetUserFromUsername(username) + if err != nil { + data["err"] = "Could not get the id four your username... Please contact an admin" + } + + bot, err := BotGetById(int(botid)) + data["bot"] = bot + data["user"] = viewer + + // open radare without input for building the bot + r2p1, err := r2pipe.NewPipe("--") + if err != nil { + panic(err) + } + defer r2p1.Close() + + src := strings.ReplaceAll(bot.Source, "\r\n", "; ") + radareCommand := fmt.Sprintf("rasm2 -a %s -b %s \"%+v\"", bot.Archs[0].Name, bot.Bits[0].Name, src) + bytecode, err := r2cmd(r2p1, radareCommand) + if err != nil { + data["err"] = "Error assembling the bot" + http.Redirect(w, r, fmt.Sprintf("/bot/%d", botid), http.StatusSeeOther) + return + } + data["bytecode_r2cmd"] = radareCommand + data["bytecode"] = bytecode + + radareCommand = fmt.Sprintf("rasm2 -a %s -b %s -D %+v", bot.Archs[0].Name, bot.Bits[0].Name, bytecode) + disasm, err := r2cmd(r2p1, radareCommand) + if err != nil { + data["err"] = "Error disassembling the bot" + http.Redirect(w, r, fmt.Sprintf("/bot/%d", botid), http.StatusSeeOther) + return + } + data["err"] = "Could not get the id four your username... Please contact an admin" + + data["disasm_r2cmd"] = radareCommand + data["disasm"] = disasm + + // define the breadcrumbs + data["pagelink2"] = Link{bot.Name, fmt.Sprintf("/%d", bot.ID)} + + allBotNames, err := BotGetAll() + var opts []Link + for _, bot := range allBotNames { + opts = append(opts, Link{Name: bot.Name, Target: fmt.Sprintf("/%d", bot.ID)}) + } + data["pagelink2options"] = opts + + editable := false + for _, user := range bot.Users { + if user.ID == viewer.ID { + editable = true + } + } + if editable == true { + data["editable"] = true + } + + // get all architectures and set the enable flag on the ones that are enabled in the battle + archs, err := ArchGetAll() + if err != nil { + data["err"] = "Could not fetch the archs" + } else { + data["archs"] = archs + } + + for i, a := range archs { + for _, b := range bot.Archs { + if a.ID == b.ID { + archs[i].Enabled = true + } + } + } + + // get all bits and set the enable flag on the ones that are enabled in the battle + bits, err := BitGetAll() + if err != nil { + data["err"] = "Could not fetch the bits" + } else { + data["bits"] = bits + } + + for i, a := range bits { + for _, b := range bot.Bits { + if a.ID == b.ID { + bits[i].Enabled = true + } + } + } + + // get the template + t, err := template.ParseGlob("./templates/*.html") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("500 - Error reading template file")) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // exec! + t.ExecuteTemplate(w, "botSingle", data) + + case "POST": + // checking if the user submitting the bot information is allowed to do so + session, _ := globalState.sessions.Get(r, "session") + username := session.Values["username"].(string) + + // get the user submitting + log.Println("Getting the user submitting the change request...") + requesting_user, err := UserGetUserFromUsername(username) + if err != nil { + log.Println("err: ", err) + http.Redirect(w, r, fmt.Sprintf("/bot/%d", botid), http.StatusSeeOther) + return + } + + // get the users the bot belongs + log.Println("Getting the user the bot belongs to...") + orig_bot, err := BotGetById(int(botid)) + if err != nil { + log.Println("err: ", err) + http.Redirect(w, r, fmt.Sprintf("/bot/%d", botid), http.StatusSeeOther) + return + } + + // check if the user submitting the change request is within the users the bot belongs to + log.Println("Checking if edit is allowed...") + allowed_to_edit := false + for _, user := range orig_bot.Users { + if user.ID == requesting_user.ID { + allowed_to_edit = true + } + } + + if allowed_to_edit == false { + http.Redirect(w, r, fmt.Sprintf("/bot/%d", botid), http.StatusSeeOther) + return + } + + // at this point, we're sure the user is allowed to edit the bot + r.ParseForm() + name := r.Form.Get("name") + source := r.Form.Get("source") + + var archIDs []int + var bitIDs []int + + for k, _ := range r.Form { + if strings.HasPrefix(k, "arch-") { + id, err := strconv.Atoi(strings.TrimPrefix(k, "arch-")) + if err != nil { + msg := "ERROR: Invalid arch id" + http.Redirect(w, r, fmt.Sprintf("/bot/%d?res=%s", botid, msg), http.StatusSeeOther) + return + } + archIDs = append(archIDs, id) + } + if strings.HasPrefix(k, "bit-") { + id, err := strconv.Atoi(strings.TrimPrefix(k, "bit-")) + if err != nil { + msg := "ERROR: Invalid bit id" + http.Redirect(w, r, fmt.Sprintf("/bot/%d?res=%s", botid, msg), http.StatusSeeOther) + return + } + bitIDs = append(bitIDs, id) + } + } + + if len(archIDs) == 0 { + msg := "ERROR: Please select an architecture" + http.Redirect(w, r, fmt.Sprintf("/bot/%d?res=%s", botid, msg), http.StatusSeeOther) + return + } + if len(archIDs) >= 2 { + msg := "ERROR: Please select ONE architecture" + http.Redirect(w, r, fmt.Sprintf("/bot/%d?res=%s", botid, msg), http.StatusSeeOther) + return + } + + if len(bitIDs) == 0 { + msg := "ERROR: Please select one of the bits" + http.Redirect(w, r, fmt.Sprintf("/bot/%d?res=%s", botid, msg), http.StatusSeeOther) + return + } + if len(bitIDs) >= 2 { + msg := "ERROR: Please select ONE of the bits" + http.Redirect(w, r, fmt.Sprintf("/bot/%d?res=%s", botid, msg), http.StatusSeeOther) + return + } + + // link archs to battle + err = BotLinkArchIDs(botid, archIDs) + if err != nil { + log.Println("Error linking the arch ids to the battle: ", err) + msg := "ERROR: Could not create due to internal reasons" + http.Redirect(w, r, fmt.Sprintf("/bot/%d?res=%s", botid, msg), http.StatusSeeOther) + return + } + + // link bits to battle + err = BotLinkBitIDs(botid, bitIDs) + if err != nil { + log.Println("Error linking the bit ids to the battle: ", err) + msg := "ERROR: Could not create due to internal reasons" + http.Redirect(w, r, fmt.Sprintf("/bot/%d?res=%s", botid, msg), http.StatusSeeOther) + return + } + + if name != "" { + if source != "" { + log.Println("Updating bot...") + err := BotUpdate(botid, name, source) + if err != nil { + log.Println("err: ", err) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("500 - Error inserting bot into db")) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } + } + + http.Redirect(w, r, fmt.Sprintf("/bot/%d", botid), http.StatusSeeOther) + + default: + http.Redirect(w, r, "/", http.StatusMethodNotAllowed) + } +} + +func botNewHandler(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "GET": + // define data + data := map[string]interface{}{} + + session, _ := globalState.sessions.Get(r, "session") + username := session.Values["username"].(string) + data["pagelink1"] = Link{Name: "bot", Target: "/bot"} + data["pagelink1options"] = []Link{ + {Name: "user", Target: "/user"}, + {Name: "battle", Target: "/battle"}, + } + data["pagelink2"] = Link{Name: "new", Target: "/new"} + data["pagelink2options"] = []Link{ + {Name: "list", Target: ""}, + } + + // display errors passed via query parameters + log.Println("[d] Getting previous results") + queryres := r.URL.Query().Get("res") + if queryres != "" { + data["res"] = queryres + } + + user, err := UserGetUserFromUsername(username) + if err != nil { + data["err"] = "Could not fetch the user" + } else { + data["user"] = user + } + + archs, err := ArchGetAll() + if err != nil { + data["err"] = "Could not fetch the archs" + } else { + data["archs"] = archs + } + + bits, err := BitGetAll() + if err != nil { + data["err"] = "Could not fetch the bits" + } else { + data["bits"] = bits + } + + // get the template + t, err := template.ParseGlob("./templates/*.html") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("500 - Error reading template file")) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // exec! + t.ExecuteTemplate(w, "botNew", data) + + case "POST": + session, _ := globalState.sessions.Get(r, "session") + username := session.Values["username"].(string) + + // parse the post parameters + r.ParseForm() + log.Println("---") + log.Println(r.Form) + log.Println("---") + + name := r.Form.Get("name") + source := r.Form.Get("source") + + if name == "" { + msg := "ERROR: Please provide a name" + http.Redirect(w, r, fmt.Sprintf("/bot/new?res=%s", msg), http.StatusSeeOther) + return + } + + if source == "" { + msg := "ERROR: Please provide some source" + http.Redirect(w, r, fmt.Sprintf("/bot/new?res=%s", msg), http.StatusSeeOther) + return + } + + var archIDs []int + var bitIDs []int + + for k, _ := range r.Form { + if strings.HasPrefix(k, "arch-") { + id, err := strconv.Atoi(strings.TrimPrefix(k, "arch-")) + if err != nil { + msg := "ERROR: Invalid arch id" + http.Redirect(w, r, fmt.Sprintf("/bot/new?res=%s", msg), http.StatusSeeOther) + return + } + archIDs = append(archIDs, id) + } + if strings.HasPrefix(k, "bit-") { + id, err := strconv.Atoi(strings.TrimPrefix(k, "bit-")) + if err != nil { + msg := "ERROR: Invalid bit id" + http.Redirect(w, r, fmt.Sprintf("/bot/new?res=%s", msg), http.StatusSeeOther) + return + } + bitIDs = append(bitIDs, id) + } + } + + if len(archIDs) == 0 { + msg := "ERROR: Please select an architecture" + http.Redirect(w, r, fmt.Sprintf("/bot/new?res=%s", msg), http.StatusSeeOther) + return + } + if len(archIDs) >= 2 { + msg := "ERROR: Please select ONE architecture" + http.Redirect(w, r, fmt.Sprintf("/bot/new?res=%s", msg), http.StatusSeeOther) + return + } + + if len(bitIDs) == 0 { + msg := "ERROR: Please select one of the bits" + http.Redirect(w, r, fmt.Sprintf("/bot/new?res=%s", msg), http.StatusSeeOther) + return + } + if len(bitIDs) >= 2 { + msg := "ERROR: Please select ONE of the bits" + http.Redirect(w, r, fmt.Sprintf("/bot/new?res=%s", msg), http.StatusSeeOther) + return + } + + botid, err := BotCreate(name, source) + if err != nil { + log.Println("Error creating the bot: ", err) + msg := "ERROR: Could not create bot" + http.Redirect(w, r, fmt.Sprintf("/bot/new?res=%s", msg), http.StatusSeeOther) + return + } + + err = UserLinkBot(username, botid) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("500 - Error adding the bot to the user")) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if len(archIDs) == 0 { + msg := "ERROR: Please select an architecture" + http.Redirect(w, r, fmt.Sprintf("/bot/new?res=%s", msg), http.StatusSeeOther) + return + } + + // link archs to battle + err = BotLinkArchIDs(botid, archIDs) + if err != nil { + log.Println("Error linking the arch ids to the bot: ", err) + msg := "ERROR: Could not create due to internal reasons" + http.Redirect(w, r, fmt.Sprintf("/bot/new?res=%s", msg), http.StatusSeeOther) + return + } + + if len(bitIDs) == 0 { + msg := "ERROR: Please select an bits" + http.Redirect(w, r, fmt.Sprintf("/bot/new?res=%s", msg), http.StatusSeeOther) + return + } + + // link bits to battle + err = BotLinkBitIDs(botid, bitIDs) + if err != nil { + log.Println("Error linking the bit ids to the bot: ", err) + msg := "ERROR: Could not create due to internal reasons" + http.Redirect(w, r, fmt.Sprintf("/bot/new?res=%s", msg), http.StatusSeeOther) + return + } + + http.Redirect(w, r, "/bot", http.StatusSeeOther) + return + default: + http.Redirect(w, r, "/", http.StatusMethodNotAllowed) + } +} -- cgit 1.4.1