func NewFile(r io.Reader) (*File, error) { rdr := tar.NewReader(r) f := new(File) gotQVM, gotComments, gotSyscalls := false, false, false L: for { hdr, err := rdr.Next() switch { case err == io.EOF: break L case err != nil: return nil, err case strings.HasSuffix(hdr.Name, ".qvm"): data, err := ioutil.ReadAll(rdr) if err != nil { return nil, err } f.QvmFile, err = qvm.NewFile(Rab(data)) if err != nil { return nil, err } gotQVM = true case strings.HasSuffix(hdr.Name, "csv"): f.CommentsFile, err = NewCommentsFile(rdr) if err != nil { return nil, err } _, _, err = f.CommentsFile.Parse() if err != nil { return nil, fmt.Errorf("Malformed comments file: %s", err) } gotComments = true case strings.HasSuffix(hdr.Name, "asm"): f.SyscallsFile, err = NewSyscallsFile(rdr) if err != nil { return nil, err } _, err = f.SyscallsFile.Parse() if err != nil { return nil, fmt.Errorf("Malformed syscalls file: %s", err) } gotSyscalls = true default: return nil, fmt.Errorf("Malformed dar: Extra file %s in archive.", hdr.Name) } } if !gotQVM || !gotComments || !gotSyscalls { return nil, fmt.Errorf("Malformed dar: Missing QVM, comments, or syscalls file.") } return f, nil }
func main() { cfFile, scFile := "", "" flag.StringVar(&cfFile, "comments", "", "Specify a file containing comments and data references") flag.StringVar(&scFile, "syscalls", "", "Specify a file defining the syscalls") flag.Parse() if flag.NArg() < 1 { fmt.Println("Must specify at least one QVM or disassembly archive!") os.Exit(-1) } f, err := os.OpenFile(flag.Arg(0), os.O_RDWR, 0600) exitErrNotNil(err) ctx := new(Context) ctx.dar = new(dar.File) ctx.dar.CommentsFile = new(dar.CommentsFile) ctx.dar.SyscallsFile = new(dar.SyscallsFile) switch { case strings.HasSuffix(flag.Arg(0), ".qvm"): ctx.dar.QvmFile, err = qvm.NewFile(f) exitErrNotNil(err) case strings.HasSuffix(flag.Arg(0), ".dar"): ctx.dar, err = dar.NewFile(f) exitErrNotNil(err) default: fmt.Println("File needs to have a .qvm or .dar extension.") os.Exit(-1) } err = f.Close() exitErrNotNil(err) ctx.disCtx, err = qvmd.NewContext(ctx.dar.QvmFile, true) exitErrNotNil(err) ctx.comments, ctx.renames, err = ctx.dar.CommentsFile.Parse() exitErrNotNil(err) ctx.disCtx.Syscalls, err = ctx.dar.SyscallsFile.Parse() exitErrNotNil(err) if cfFile != "" { commentsFile, err := os.OpenFile(cfFile, os.O_RDWR, 0600) exitErrNotNil(err) ctx.dar.CommentsFile, err = dar.NewCommentsFile(commentsFile) exitErrNotNil(err) ctx.comments, ctx.renames, err = ctx.dar.CommentsFile.Parse() exitErrNotNil(err) err = commentsFile.Close() exitErrNotNil(err) } if scFile != "" { syscallsFile, err := os.OpenFile(scFile, os.O_RDWR, 0600) exitErrNotNil(err) ctx.dar.SyscallsFile, err = dar.NewSyscallsFile(syscallsFile) exitErrNotNil(err) ctx.disCtx.Syscalls, err = ctx.dar.SyscallsFile.Parse() exitErrNotNil(err) err = syscallsFile.Close() exitErrNotNil(err) } for num, rename := range ctx.renames { if _, exists := ctx.disCtx.Procs[num]; exists { ctx.disCtx.Procs[num].Name = rename } } stdin := bufio.NewReader(os.Stdin) for { fmt.Print("qvmd> ") input, err := stdin.ReadString(byte('\n')) exitErrNotNil(err) cmd := strings.SplitN(strings.TrimSpace(input), " ", -1) if strings.ToLower(cmd[0]) == "quit" || err == io.EOF { fmt.Print("\n") os.Exit(0) } switch cmd[0] { case "help": fmt.Println(" comments - Print all comments") fmt.Println("comment <insnNum> <comment> - Assign a comment to instruction number <insnNum>") fmt.Println(" dis[as[semble]] <funcName> - Disassemble function <funcName>") fmt.Println(" disi <insnNum> - Disassemble function containing instruction <insnNum>") fmt.Println(" header - Print the header for the QVM file") fmt.Println(" info <funcName> - Print information about function <funcName>") fmt.Println(" infoi <insnNum> - Print information about function containing instruction <insnNum>") fmt.Println(" ren[ame] <orig> <new> - Rename function <orig> to <new>") fmt.Println(" save [tgtDar] - Save your disassembly. If opened as a QVM [tgtDar] is required") fmt.Println(" savecomments [tgtCsv] - Save all comments and renamed functions") fmt.Println(" savesyscalls [tgtAsm] - Save all syscalls") fmt.Println(" sref <string> - Search for functions referencing strings containing <string>") fmt.Println(" syscalls - Print all known syscalls") case "comments": for num, comment := range ctx.comments { fmt.Printf("0x%08x: %s\n", num, comment) } case "comment": if len(cmd) < 3 { fmt.Println("Usage: comment <insnNum> <comment>") break } insn, err := strconv.ParseUint(cmd[1], 0, 64) if err != nil { fmt.Println("Usage: comment <insnNum> <comment>") break } goAhead := true if _, exists := ctx.comments[int(insn)]; exists { fmt.Print("Overwrite existing comment? [Y/n]: ") Ans1: for { answer, err := stdin.ReadString(byte('\n')) if err != nil { fmt.Println(err) break } answer = strings.ToLower(strings.TrimSpace(answer)) switch answer { case "n", "no": goAhead = false break Ans1 case "y", "yes", "": break Ans1 default: fmt.Print("Please answer \"yes\" or \"no\": ") } } } if goAhead { ctx.comments[int(insn)] = strings.Join(cmd[2:], " ") } else { fmt.Println("Comment not replaced.") } case "dis", "disas", "disassemble": if len(cmd) < 2 { fmt.Printf("Usage: %s <funcName>\n", cmd[0]) break } found := false for _, proc := range ctx.disCtx.Procs { if proc.Name == cmd[1] { disassemble(ctx, proc) found = true break } } if !found { fmt.Printf("No function named \"%s\" found.\n", cmd[1]) } case "disi": if len(cmd) < 2 { fmt.Println("Usage: disi <insnNum>") break } tgt, err := strconv.ParseUint(cmd[1], 0, 64) if err != nil { fmt.Println(err) break } found := false for _, proc := range ctx.disCtx.Procs { if int(tgt) >= proc.StartInstruction && int(tgt) < proc.StartInstruction+proc.InstructionCount { disassemble(ctx, proc) found = true break } } if !found { fmt.Printf("No function containing instruction %d\n", tgt) } case "header": printHeader(ctx.dar.QvmFile) case "info": if len(cmd) < 2 { fmt.Println("Usage: info <funcName>") break } found := false for _, proc := range ctx.disCtx.Procs { if proc.Name == cmd[1] { printInfo(ctx, proc) found = true break } } if !found { fmt.Printf("No function named \"%s\" found.\n", cmd[1]) } case "infoi": if len(cmd) < 2 { fmt.Println("Usage: infoi <insnNum>") break } tgt, err := strconv.ParseUint(cmd[1], 0, 64) if err != nil { fmt.Println(err) break } found := false for _, proc := range ctx.disCtx.Procs { if int(tgt) >= proc.StartInstruction && int(tgt) < proc.StartInstruction+proc.InstructionCount { printInfo(ctx, proc) found = true break } } if !found { fmt.Printf("No function containing instruction %d\n", tgt) } case "ren", "rename": if len(cmd) < 3 { fmt.Printf("Usage: %s <orig> <new>\n", cmd[0]) break } found := false for _, proc := range ctx.disCtx.Procs { if proc.Name == cmd[1] { proc.Name = cmd[2] found = true ctx.renames[proc.StartInstruction] = cmd[2] } } if !found { fmt.Printf("No function named \"%s\" found.\n", cmd[1]) } case "save": tgtFile := flag.Arg(0) if len(cmd) >= 2 { tgtFile = strings.Join(cmd[1:], " ") } if strings.HasSuffix(tgtFile, ".qvm") { fmt.Println("Opened as a single QVM. Please save to a new dar") break } if err := ctx.dar.CommentsFile.Write(ctx.comments, ctx.renames); err != nil { fmt.Println(err) break } f, err = os.OpenFile(tgtFile, os.O_RDWR|os.O_CREATE, 0600) if err != nil { fmt.Println(err) } err = f.Truncate(0) if err != nil { fmt.Println(err) f.Close() break } _, err = f.Seek(0, 0) if err != nil { fmt.Println(err) f.Close() break } err = ctx.dar.WriteTo(f) if err != nil { fmt.Println(err) f.Close() break } err = f.Sync() if err != nil { fmt.Println(err) f.Close() break } err = f.Close() if err != nil { fmt.Println(err) } case "savecomments": tgtFile := cfFile if len(cmd) >= 2 { tgtFile = strings.Join(cmd[1:], " ") } if tgtFile == "" { fmt.Println("Usage: savecomments <file>") break } if err := ctx.dar.CommentsFile.Write(ctx.comments, ctx.renames); err != nil { fmt.Println(err) break } f, err := os.OpenFile(tgtFile, os.O_RDWR|os.O_CREATE, 0600) if err != nil { fmt.Println(err) break } err = f.Truncate(0) if err != nil { fmt.Println(err) f.Close() break } _, err = f.Seek(0, 0) if err != nil { fmt.Println(err) f.Close() break } if n, err := f.Write(ctx.dar.CommentsFile.Data); err != nil || n != len(ctx.dar.CommentsFile.Data) { fmt.Printf("Error writing comments: %s\n", err) f.Close() break } err = f.Close() if err != nil { fmt.Println(err) } case "savesyscalls": tgtFile := scFile if len(cmd) >= 2 { tgtFile = strings.Join(cmd[1:], " ") } if tgtFile == "" { fmt.Println("savesyscalls <file>") break } f, err := os.OpenFile(tgtFile, os.O_RDWR|os.O_CREATE, 0600) if err != nil { fmt.Println(err) break } err = f.Truncate(0) if err != nil { fmt.Println(err) f.Close() break } _, err = f.Seek(0, 0) if err != nil { fmt.Println(err) f.Close() break } if n, err := f.Write(ctx.dar.SyscallsFile.Data); err != nil || n != len(ctx.dar.SyscallsFile.Data) { fmt.Printf("Error writing syscalls: %s\n", err) f.Close() break } err = f.Close() if err != nil { fmt.Println(err) } case "sref": if len(cmd) < 2 { fmt.Println("Usage: sref <string>") } found := false for _, proc := range ctx.disCtx.Procs { for i := proc.StartInstruction; i < proc.StartInstruction+proc.InstructionCount; i++ { if ctx.disCtx.Insns[i].Op == qvmd.OP_CONST && ctx.disCtx.Insns[i+1].Op != qvmd.OP_CALL { tgtBuf := bytes.NewBuffer(ctx.disCtx.Insns[i].Arg) var tgt uint32 if err := binary.Read(tgtBuf, binary.LittleEndian, &tgt); err != nil { fmt.Println(err) } if tgt >= ctx.dar.QvmFile.Header.DataLength && tgt < ctx.dar.QvmFile.Header.DataLength+ctx.dar.QvmFile.Header.LitLength { if str, exists := ctx.disCtx.Strings[int(tgt)]; exists { if strings.Contains(str, strings.Join(cmd[1:], " ")) { fmt.Println(proc.Name) found = true } } } } } } if !found { fmt.Printf("No functions containing \"%s\"\n", strings.Join(cmd[1:], " ")) } case "syscalls": keys := make([]int, len(ctx.disCtx.Syscalls)) for key, _ := range ctx.disCtx.Syscalls { keys = append(keys, key) } sort.Sort(sort.IntSlice(keys)) for _, key := range keys { fmt.Printf("%d: %s\n", key, ctx.disCtx.Syscalls[key].Name) } } } }