From dabb7ef2dcfb238b4610930668e8e1dc9ea48b40 Mon Sep 17 00:00:00 2001 From: Jas Laferriere Date: Wed, 31 Aug 2022 13:19:45 -0400 Subject: [PATCH] add list command to write out all injections primarily we intend to use this to generate a list of codes to add to our playback denylist by looking at the tags value --- gecko.go | 33 ++++++++- list.go | 211 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 list.go diff --git a/gecko.go b/gecko.go index ca9fda1..e4c49ab 100644 --- a/gecko.go +++ b/gecko.go @@ -67,6 +67,7 @@ type asmFileHeader struct { Address string Codetype string Annotation string + Tags string } var argConfig assemblerArgConfig @@ -172,6 +173,25 @@ func main() { outputFiles = append(outputFiles, FileDetails{File: *outputFilePtr}) output = generateInjectionFolderLines(*assemblePathPtr, *isRecursivePtr) + case "list": + listFlags := flag.NewFlagSet("list", flag.ExitOnError) + inputPtr := listFlags.String( + "i", + "codes.json", + "Input to use for generating the list. Can be a json file as used with build or a path as used with assemble.", + ) + outputFilePtr := listFlags.String( + "o", + "injection-list.json", + "Output file name where the list will be saved.", + ) + isRecursivePtr := listFlags.Bool( + "r", + true, + "If true, will recursively find all .asm files within the sub-directories as well as the root directory.", + ) + listFlags.Parse(os.Args[2:]) + listInjections(*inputPtr, *outputFilePtr, *isRecursivePtr) case "-h": // Print help information fmt.Println("Usage: gecko [flags]") @@ -179,6 +199,7 @@ func main() { fmt.Println("Supported commands:") fmt.Println("\tbuild - Uses a configuration file to build codes. Recommended for larger projects.") fmt.Println("\tassemble - Assembles asm files in a given directory.") + fmt.Println("\tlist - Outputs a list of all the injections ") fmt.Println() fmt.Println("Use gecko -h for information about the flags for the different commands") os.Exit(1) @@ -348,7 +369,7 @@ func addLineAnnotation(line, annotation string) string { return fmt.Sprintf("%s #%s", line, annotation) } -func generateInjectionFolderLines(rootFolder string, isRecursive bool) []string { +func collectFilesFromFolder(rootFolder string, isRecursive bool) []string { asmFilePaths := []string{} // First collect all of the asm files we need to process @@ -390,6 +411,12 @@ func generateInjectionFolderLines(rootFolder string, isRecursive bool) []string folders = append(newFolders, folders...) } + return asmFilePaths +} + +func generateInjectionFolderLines(rootFolder string, isRecursive bool) []string { + asmFilePaths := collectFilesFromFolder(rootFolder, isRecursive) + resultsChan := make(chan lineAggregateResult, len(asmFilePaths)) for idx, filePath := range asmFilePaths { go func(filePath string, orderNum int) { @@ -438,7 +465,7 @@ func parseAsmFileHeader(filePath string) asmFileHeader { log.Panic(errMsg + "\n") } - result := asmFileHeader{"", "Auto", ""} + result := asmFileHeader{"", "Auto", "", ""} // Read header lines from file scanner := bufio.NewScanner(file) @@ -475,6 +502,8 @@ func parseAsmFileHeader(filePath string) asmFileHeader { result.Codetype = value case "Annotation:": result.Annotation = value + case "Tags:": + result.Tags = value } } diff --git a/list.go b/list.go new file mode 100644 index 0000000..4425799 --- /dev/null +++ b/list.go @@ -0,0 +1,211 @@ +package main + +import ( + "bytes" + "encoding/binary" + "encoding/hex" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" + "strings" +) + +type injectionDetails struct { + InjectionAddress string + Codetype string + Annotation string + Tags string +} + +type injectionList struct { + Details []injectionDetails +} + +func listInjections(input, output string, isRecursive bool) { + result := injectionList{} + + loc, err := os.Stat(input) + if err != nil { + log.Panicf("Could not find input %s for injection list.", input) + } + + if loc.IsDir() { + populateInjectionsFromFolder(input, isRecursive, &result) + } else { + populateInjectionsFromFile(input, &result) + } + + // Write out the results + writeInjectionList(output, &result) +} + +func writeInjectionList(output string, list *injectionList) { + contents, err := json.MarshalIndent(list, "", " ") + if err != nil { + log.Panicf("Failed to prepare injection list data for writing. %s", err.Error()) + } + + err = ioutil.WriteFile(output, contents, 0644) + if err != nil { + log.Panicf("Failed to write injection list file to: %s. %s", output, err.Error()) + } +} + +func populateInjectionsFromFile(input string, list *injectionList) { + config := readConfigFile(input) + for _, code := range config.Codes { + for _, geckoCode := range code.Build { + switch geckoCode.Type { + case Inject: + header := parseAsmFileHeader(geckoCode.SourceFile) + codetype := header.Codetype + address := header.Address + if codetype == "" { + codetype = geckoCode.Type + } + if address == "" { + address = geckoCode.TargetAddress + } + // Compile file and add lines + lineAnnotation := filepath.ToSlash(geckoCode.SourceFile) + if header.Annotation != "" { + lineAnnotation = fmt.Sprintf("%s | %s", header.Annotation, lineAnnotation) + } + if geckoCode.Annotation != "" { + lineAnnotation = fmt.Sprintf("%s | %s", geckoCode.Annotation, lineAnnotation) + } + + // Make sure address format is good + address, err := parseAddressFromString(address) + if err != nil { + log.Panicf("Address %s has invalid format for injection entry in: %s. %s", address, input, err.Error()) + } + + list.Details = append(list.Details, injectionDetails{ + InjectionAddress: address, + Codetype: codetype, + Annotation: lineAnnotation, + }) + case ReplaceBinary: + // Make sure address format is good + address, err := parseAddressFromString(geckoCode.Address) + if err != nil { + log.Panicf("Address %s has invalid format for replace binary entry in: %s. %s", address, input, err.Error()) + } + + list.Details = append(list.Details, injectionDetails{ + InjectionAddress: address, + Codetype: "06", + Annotation: geckoCode.Annotation, + }) + case Binary: + populateInjectionsFromBinary(geckoCode.SourceFile, list) + case InjectFolder: + populateInjectionsFromFolder(geckoCode.SourceFolder, geckoCode.IsRecursive, list) + default: + log.Panicf("Unsupported build type: %s\n", geckoCode.Type) + } + } + } +} + +func populateInjectionsFromBinary(file string, list *injectionList) { + contents, err := ioutil.ReadFile(file) + if err != nil { + log.Panicf("Failed to read binary file %s\n%s\n", file, err.Error()) + } + + instructions := contents + + if len(instructions) == 0 { + log.Panicf("Binary file must not be empty: %s\n", file) + } + + // Fixes code to have an even number of words + if len(instructions)%8 != 0 { + log.Panicf("Binary file must have byte count divisable by 8: %s\n", file) + } + + i := 0 + for i < len(instructions) { + codetype := strings.ToUpper(hex.EncodeToString([]byte{instructions[i] & 0xFE})) + address := instructions[i : i+4] + address[0] = (address[0] & 1) + 0x80 + + list.Details = append(list.Details, injectionDetails{ + InjectionAddress: strings.ToUpper(hex.EncodeToString(address)), + Codetype: codetype, + }) + + // Move to next code in the list + switch codetype { + case "04": + i += 8 + case "06": + var byteLen uint64 + b := []byte{0, 0, 0, 0, instructions[i+4], instructions[i+5], instructions[i+6], instructions[i+7]} + err := binary.Read(bytes.NewReader(b), binary.BigEndian, &byteLen) + if err != nil { + log.Panicf("Idx: %d. Failed to parse size on 06 code in binary: %s. %s", i, file, err.Error()) + } + i += 8 + ((int(byteLen) + 7) & 0xFFFFFFF8) // Round up to next 8 bytes and add the first 8 bytes + case "08": + i += 16 + case "C2": + var lineCount uint64 + b := []byte{0, 0, 0, 0, instructions[i+4], instructions[i+5], instructions[i+6], instructions[i+7]} + err := binary.Read(bytes.NewReader(b), binary.BigEndian, &lineCount) + if err != nil { + log.Panicf("Idx: %d. Failed to parse size on C2 code in binary: %s. %s", i, file, err.Error()) + } + i += 8 + (int(lineCount) * 8) + default: + log.Panicf("Idx: %d. Codetype %s in binary %s not supported.", i, codetype, file) + } + } +} + +func populateInjectionsFromFolder(input string, isRecursive bool, list *injectionList) { + asmFilePaths := collectFilesFromFolder(input, isRecursive) + + for _, filePath := range asmFilePaths { + header := parseAsmFileHeader(filePath) + address := parseAddressFromFile(header.Address, filePath) + list.Details = append(list.Details, injectionDetails{ + InjectionAddress: address, + Codetype: header.Codetype, + Annotation: header.Annotation, + Tags: header.Tags, + }) + } +} + +func parseAddressFromString(address string) (string, error) { + str := strings.TrimSpace(address) + if strings.HasPrefix(str, "0x") { + str = str[2:10] + } else { + str = str[:8] + } + + _, err := hex.DecodeString(str) + if err != nil { + return "", err + } + + return strings.ToUpper(str), nil +} + +func parseAddressFromFile(headerAddress, filePath string) string { + address, err := parseAddressFromString(headerAddress) + if err == nil { + return address + } + + // Here the address is probably a symbol, we need to actually compile the file to get the address + _, address = compile(filePath, headerAddress) + return strings.ToUpper(address) +}