fxos/shell/a.cpp

280 lines
7.4 KiB
C++

#include "shell.h"
#include "parser.h"
#include "commands.h"
#include "errors.h"
#include "util.h"
#include <fxos/function.h>
#include <fxos/vspace.h>
#include <fxos/util/Timer.h>
#include <fxos/util/log.h>
#include <fmt/core.h>
#include <endian.h>
// TODO: fxos: Proper definition of function analysis version
#define FXOS_FUNCTION_ANALYSIS_VERSION 1
//---
// af
//---
struct _af_args
{
bool update = false;
bool force = false;
std::string name = "";
std::vector<u32> addresses;
};
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.accept_options();
do {
args.addresses.push_back(parser.expr(session.currentBinary()));
}
while(!parser.at_end());
parser.end();
return args;
}
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;
int const FAV = FXOS_FUNCTION_ANALYSIS_VERSION;
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 || existing->analysisVersion() < FAV) {
auto f = std::make_unique<Function>(binary, entry);
if(f->exploreFunctionAt(entry)) {
f->updateFunctionSize();
f->setAnalysisVersion(FAV);
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());
/* TODO: Check for overlapping functions etc */
}
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");
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>...]
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.
When a single address is given, -n can specify the name of the function object
to be created.
)");
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.
)");