#include "fxos-cli.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace fs = std::filesystem; using namespace FxOS; static char const *help_string = R"( usage: fxos info fxos disasm [options...] fxos analyze [-f] [-s] [-a] [-r] [options...] fxos is a reverse-engineering tool that disassembles and analyzes SuperH programs and OS dumps for fx9860g and fxcg50-like CASIO calculators, using an editable database of platform, syscall, and OS knowledge. General options: -3, --sh3 Assume SH3 OS and platform (default: SH4) -4, --sh4 Assume SH4 OS and platform (default: SH4) A is either: A target in library (eg "fx@3.10") -f An arbitrary file which is loaded as ROM INFO COMMAND Identify an OS image: version, platform, date, checksums... DISASM COMMAND Disassemble and annotate code with relative address targets, syscalls, control flow, propagated constants and hints about memory structure. Location specifiers:
Start disassembling at this address (hexa)
: Disassemble exactly the specified region. is an hexadecimal number optionally followed by k, M, or G. % Start disassembling at this syscall's address (hexa) Disassemble this library symbol (typically a syscall name). Note that
takes precedence if ambiguous. Disassembly options: -p Execute the specified comma-separated list of passes --load Read additional documentation from --load Read additional documentation recursively from Available passes: cfg Build the control flow graph (always required) pcrel Resolve PC-relative references as their target address cstprop Propagate constants by abstract interpretation syscall Annotate code with reverse syscalls regs Annotate code with peripheral register addresses The default sequence of passes is cfg,pcrel,cstprop,syscall,regs. When disassembling a function (ie. no size specified on the command-line), the cfg pass is always executed to explore the function. ANALYZE COMMAND Dig an address or syscall number. Finds syscall references, 4-aligned occurrences, memory region... Analysis modes: -f, --full Run all analysis passes on (same as -sar) -s, --syscall Run syscall ID analysis -a, --address Run code/data address analyis -r, --register Run peripheral register analysis Analysis options: --occurrences Show at most occurrences (integer or "all") )"+1; //--- // Configuration //--- std::map targets; std::vector library { FXOS_INSTALL_PREFIX "/share/fxos" }; /* Load any fxos data file */ void load(std::string path) { Buffer file(path); size_t offset; int line; // std::cerr << "[fxos] loading resource file '" << path << "'...\n"; Header h = load_header(file, offset, line); if(h.find("type") == h.end()) { std::cerr << "error: no type in header of '" << path << "'\n"; return; } std::string type = h["type"]; if(type == "assembly") { try { load_asm(file, offset, line); } catch(FxOS::SyntaxError &e) { std::cerr << e.file() << ":" << e.line() << ": " << e.what() << "\n" << std::flush; } return; } else if(type == "target") { if(!h.count("name")) { std::cerr << "error: no name specified in '" << path << "'\n"; return; } try { targets[h["name"]] = load_target(file, offset, line); } catch(FxOS::SyntaxError &e) { std::cerr << e.file() << ":" << e.line() << ": " << e.what() << "\n" << std::flush; } return; } std::cerr << "unknown file type '" << type << "' in '" << path <<"'\n"; } /* Load a whole folder into the database */ void loadfolder(std::string path) { try { fs::recursive_directory_iterator it(path); for(auto &file: it) load(file.path()); } catch(fs::filesystem_error &e) { if(e.code().value() == ENOENT) { std::cerr << "warning: directory '" << path << "' does" " not exist\n"; } else throw e; } } void loadconfig(void) { std::string home = getenv("HOME"); fs::path configpath = home + "/.config/fxos/config"; if(!fs::exists(configpath)) return; std::ifstream stream(configpath); /* Read line by line and register paths for the library or load files from the database */ while(stream) { char path[256]; std::string line; std::getline(stream, line); if(std::sscanf(line.c_str(), "library: %256s", path) == 1) { library.push_back(path); } else if(std::sscanf(line.c_str(), "load: %256s", path) == 1) { loadfolder(path); } } } //--- // Main routines //--- int main_info(int argc, char **argv) { int error=0, option=0, mpu='4'; std::string path; struct option const longs[] = { { "help", no_argument, NULL, 'h' }, { "sh3", no_argument, NULL, '3' }, { "sh4", no_argument, NULL, '4' }, }; while(option >= 0 && option != '?') switch((option = getopt_long(argc, argv, "h34f:p:", longs, NULL))) { case 'h': std::cerr << help_string; break; case '3': case '4': /* TODO: Use sh3/sh4 information in [fxos info]? */ mpu = option; break; case 'f': path = optarg; break; case '?': error = 1; } if(error) return 1; /* Load from path if one is specified */ if(path.size()) { try { /* Load the file in ROM over 8M */ Buffer romfile(path, MemoryRegion::ROM.size()); Target t; t.bind_region(MemoryRegion::ROM, romfile); t.bind_region(MemoryRegion::ROM_P2, romfile); os_info(t); } catch(std::exception &e) { std::cerr << "error: " << e.what() << "\n"; return 1; } } /* Load from target otherwise */ else { if(!argv[optind + 1]) { std::cerr << help_string; return 1; } std::string targetname = argv[optind + 1]; if(!targets.count(targetname)) { std::cerr << "error: no target '" << targetname << "' in library\n"; return 1; } Target t(targets[targetname], library); os_info(t); } return 0; } int main_disassembly(int argc, char **argv) { int error=0, option=0, mpu='4'; std::vector passes { "cfg", "pcrel", "constprop", "syscall", "regs" }; std::string file; struct option const longs[] = { { "help", no_argument, NULL, 'h' }, { "sh3", no_argument, NULL, '3' }, { "sh4", no_argument, NULL, '4' }, }; while(option >= 0 && option != '?') switch((option = getopt_long(argc, argv, "h34p:f:", longs, NULL))) { case 'h': std::cerr << help_string; break; case '3': case '4': mpu = option; break; case 'p': passes.clear(); { std::istringstream in(optarg); std::string pass; while(std::getline(in, pass, ',')) { passes.push_back(pass); } } break; case 'f': file = optarg; break; case '?': error = 1; } int remaining_args = (file.size() ? 1 : 2); if(argc < optind + remaining_args + 1) { std::cerr << "error: missing file or address\n"; error = 1; } else if(argc > optind + remaining_args + 1) { std::cerr << "error: excess arguments\n"; error = 1; } if(error) return 1; if(!file.size()) { std::string targetname = argv[optind + 1]; if(!targets.count(targetname)) { std::cerr << "error: no target '" << targetname << "' in library\n"; return 1; } Target target(targets[targetname], library); char const *refstr = argv[optind + 2]; uint32_t ref; sscanf(refstr, "%x", &ref); Disassembly disasm(target); OS *os = nullptr; std::cout << "disassembling target:" << targetname << " ref:" << refstr << "\n"; for(auto pass: passes) { std::cout << "running pass: " << pass << "\n"; if(pass == "cfg") { CfgPass p(disasm); p.run(ref); } } } else { char const *ref = argv[optind + 1]; std::cout << "disassembling file:" << file << " ref:" << ref << "\n"; } return 0; } int main_analyze(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) { std::cerr << "doing main_analyze, which is incomplete x_x\n"; return 0; } int main(int argc, char **argv) { if(argc < 2) { std::cerr << help_string; return 1; } std::string cmd = argv[1]; argv[1] = (char *)""; /* Load the configuration file if it exists */ loadconfig(); if(cmd == "info") return main_info(argc, argv); else if(cmd == "disasm") return main_disassembly(argc, argv); else if(cmd == "analyze") return main_analyze(argc, argv); else if(cmd == "-?" || cmd == "-h" || cmd == "--help") { std::cerr << help_string; return 0; } std::cerr << "invalid operation '" << cmd << "'\n"; std::cerr << "Try '" << argv[0] << " --help'.\n"; return 1; }