fxos/shell/a.cpp

346 lines
9.4 KiB
C++

#include "shell.h"
#include "parser.h"
#include "commands.h"
#include "errors.h"
#include "util.h"
#include <fxos/function.h>
#include <fxos/analysis.h>
#include <fxos/vspace.h>
#include <fxos/view/util.h>
#include <fxos/util/Timer.h>
#include <fxos/util/log.h>
#include <fmt/core.h>
#include <endian.h>
//---
// af
//---
struct _af_args
{
bool update = false;
bool force = false;
std::string name = "";
std::vector<u32> addresses;
bool consistency = false;
};
static _af_args parse_af(Session &session, Parser &parser)
{
_af_args args;
parser.option("-u", [&args](Parser &) { args.update = true; });
parser.option("--force", [&args](Parser &) { args.force = true; });
parser.option("-n", [&args](Parser &p) { args.name = p.symbol(""); });
parser.option("-c", [&args](Parser &) { args.consistency = true; });
parser.accept_options();
if(args.consistency && parser.at_end())
return args;
do {
args.addresses.push_back(parser.expr(session.currentBinary()));
}
while(!parser.at_end());
parser.end();
return args;
}
static void _af_consistency(Binary const &binary)
{
/* List of functions with blocks before the function's entry point */
std::vector<Function const *> earlyBlocks;
/* List of functions with no returning block */
std::vector<Function const *> noReturn;
for(Function const &f: binary.functions()) {
if(std::any_of(f.begin(), f.end(),
[&f](auto &bb) { return bb.address() < f.address(); }))
earlyBlocks.push_back(&f);
if(!std::any_of(f.begin(), f.end(),
[&f](auto &bb) { return bb.isTerminator(); }))
noReturn.push_back(&f);
}
ViewStringsOptions opts = {
.maxColumns = 78,
.lineStart = " ",
.separator = " ",
};
if(earlyBlocks.size() > 0) {
fmt::print("{} functions have blocks before their entry point:\n",
earlyBlocks.size());
viewStrings(
earlyBlocks,
[](Function const *f) {
if(f->name().size() > 0)
return fmt::format("{}", f->name());
else
return fmt::format("fun.{:08x}", f->address());
},
opts);
}
if(noReturn.size() > 0) {
fmt::print("{} functions do not return:\n", noReturn.size());
viewStrings(
noReturn,
[](Function const *f) {
if(f->name().size() > 0)
return fmt::format("{}", f->name());
else
return fmt::format("fun.{:08x}", f->address());
},
opts);
}
}
static void af_analyze(Binary &binary, _af_args const &args)
{
int successes = 0, skipped = 0, errors = 0;
Timer timer;
timer.start();
auto const &addresses = args.addresses;
for(int i = 0; i < (int)addresses.size(); i++) {
u32 entry = addresses[i];
printr("[%d/%zu] Analyzing 0x%08x...", i + 1, addresses.size(), entry);
/* Check if there is already a function defined here */
Function *existing = binary.functionAt(entry);
if(!existing) {
auto f = std::make_unique<Function>(binary, entry);
if(f->exploreFunctionAt(entry)) {
f->updateFunctionSize();
f->runAnalysis();
binary.addObject(std::move(f));
successes++;
}
else {
FxOS_log(ERR, "... while analyzing 0x%08x", entry);
errors++;
}
}
else {
skipped++;
}
/* TODO: Queue subfunctions for recursive analysis */
}
timer.stop();
printf("\nAnalyzed %d functions (+%d skipped, +%d errors) in %s\n",
successes, skipped, errors, timer.format_time().c_str());
// _af_consistency(binary);
}
void _af(Session &session, _af_args const &args)
{
Binary *b = session.currentBinary();
if(!b)
return FxOS_log(ERR, "No current binary!\n");
af_analyze(*b, args);
}
//--
// afs
//---
static _af_args parse_afs(Session &, Parser &parser)
{
_af_args args;
parser.option("-u", [&args](Parser &) { args.update = true; });
parser.option("--force", [&args](Parser &) { args.force = true; });
parser.accept_options();
parser.end();
return args;
}
void _afs(Session &session, _af_args &args)
{
Binary *b = session.currentBinary();
if(!b)
return FxOS_log(ERR, "No current binary!\n");
if(args.consistency) {
_af_consistency(*b);
return;
}
OS *os = b->OSAnalysis();
if(!os)
return FxOS_log(ERR, "afs: No OS analysis, cannot enumerate syscalls");
// TODO: afs: Use syscall info
for(int i = 0; i < os->syscall_count(); i++)
args.addresses.push_back(os->syscall(i));
af_analyze(*b, args);
}
//---
// am
//---
static void _am_cg_main_menu_function(Binary &b)
{
VirtualSpace &vspace = b.vspace();
OS *os = b.OSAnalysis();
if(!os) {
FxOS_log(ERR, "no OS analysis");
return;
}
if(os->syscall_count() < 0x1e58) {
FxOS_log(ERR, "less than 0x1e58 syscalls");
return;
}
uint32_t sc_addr = os->syscall(0x1e58);
fmt::print("syscall %%1e58 found at 0x{:08x}\n", sc_addr);
/* Check up to 150 instructions to find the call to the internal function
SaveAndOpenMainMenu(). This call is in a widget of the shape
mov.l GetkeyToMainFunctionReturnFlag, rX
mov #3, rY
bsr SaveAndOpenMainMenu
mov.b rY, @rX
bra <start of widget>
nop */
for(int i = 0; i < 150; i++) {
uint16_t i0 = vspace.read_u16(sc_addr + 2 * (i + 0));
uint16_t i1 = vspace.read_u16(sc_addr + 2 * (i + 1));
uint16_t i2 = vspace.read_u16(sc_addr + 2 * (i + 2));
uint16_t i3 = vspace.read_u16(sc_addr + 2 * (i + 3));
uint16_t i4 = vspace.read_u16(sc_addr + 2 * (i + 4));
uint16_t i5 = vspace.read_u16(sc_addr + 2 * (i + 5));
/* Match: mov.l @(disp, pc), rX */
if((i0 & 0xf000) != 0xd000)
continue;
int rX = (i0 >> 8) & 0x0f;
/* Match: mov #3, rY */
if((i1 & 0xf0ff) != 0xe003)
continue;
int rY = (i1 >> 8) & 0x0f;
/* Match: bsr @(disp, pc) */
if((i2 & 0xf000) != 0xb000)
continue;
int disp = (i2 & 0x0fff);
/* Match: mov.b rX, @rY */
if((i3 != 0x2000 + (rX << 8) + (rY << 4)))
continue;
/* Match: bra @(_, pc) */
if((i4 & 0xf000) != 0xa000)
continue;
/* Match: nop */
if(i5 != 0x0009)
continue;
/* Return the target of the bsr instruction */
uint32_t fun_addr = sc_addr + 2 * (i + 2) + 4 + disp * 2;
fmt::print("found widget at 0x{:08x}\n", sc_addr + 2 * i);
fmt::print("rX = r{}, rY = r{}, disp = {}\n", rX, rY, disp);
fmt::print("main menu function address: 0x{:08x}\n", fun_addr);
}
}
static std::string parse_am(Session &session, Parser &parser)
{
if(!session.currentBinary())
return "";
std::string name = parser.symbol();
parser.end();
return name;
}
void _am(Session &session, std::string name)
{
if(!session.currentBinary()) {
FxOS_log(ERR, "am: no current binary");
return;
}
if(name == "cg_main_menu_function")
_am_cg_main_menu_function(*session.currentBinary());
else
FxOS_log(ERR, "am: unknown misc. command '%s'", name);
}
//---
// Command definitions
//---
static ShellCommand _af_cmd(
"af",
[](Session &s, Parser &p) {
auto args = parse_af(s, p);
_af(s, args);
},
[](Session &s, Parser &p) { parse_af(s, p); }, "Analysis: Functions", R"(
af [-u|--force] [-n <name>] [<addresses>...]
af -c
Explore and disassemble functions starting at the specified addresses. For each
explored function, a binary object of Function type is created, and the
function is statically analyzed.
By default, addresses where functions already exist are not reanalyzed.
Specifying -u (update) causes all functions to be re-processed, while keeping
user-specified information (name, prototype, etc). Specifying --force causes
all functions to be reanalyzed from scratch without keeping user-specified
information.
-> TODO: Currently, no difference.
When a single address is given, -n can specify the name of the function object
to be created.
With -c (consistency), does not create a new function, but checks the
consistency of the set of functions defined in the current binary, and reports
any suspicious occurrences, such as functions with blocks before the entry
address, overlapping functions, functions with no rts, etc.
)");
static ShellCommand _afs_cmd(
"afs",
[](Session &s, Parser &p) {
auto args = parse_afs(s, p);
_afs(s, args);
},
[](Session &s, Parser &p) { parse_afs(s, p); },
"Analysis: Functions (Syscalls)", R"(
afs [-u|--force]
Explore and disassemble syscalls. Like af, but automatically pulls function
names and prototypes out of the predefined syscall table, when there is one.
)");
static ShellCommand _am_cmd(
"am",
[](Session &s, Parser &p) {
auto name = parse_am(s, p);
_am(s, name);
},
[](Session &s, Parser &p) { parse_am(s, p); }, "Analysis: Misc functions",
R"(
am <name>
Runs miscellaneous analysis functions; commonly used for prototyping.
)");