rms-runtime-mobile-security
Version:
Runtime Mobile Security (RMS), powered by FRIDA, is a powerful web interface that helps you to manipulate Android and iOS Apps at Runtime
1,643 lines (1,432 loc) • 47.1 kB
JavaScript
const http = require('http');
const express = require("express")
const nunjucks = require('nunjucks')
const bodyParser = require('body-parser');
let frida;
const fs = require('fs');
const socket_io = require('socket.io');
const datetime = require('node-datetime');
const BETA = false
const FRIDA_DEVICE_OPTIONS=["USB","Remote","ID"]
const FRIDA_DEVICE_ARGS_OPTIONS=
{
'host': 'IP:PORT',
'id': 'Device’s serial number'
}
//PATH files
const FRIDA_AGENT_PATH = __dirname+"/agent/compiled_RMS_core.js"
const CONFIG_FILE_PATH = __dirname+"/config/config.json"
const API_MONITOR_FILE_PATH = __dirname+"/config/api_monitor.json"
const CUSTOM_SCRIPTS_PATH = __dirname+"/custom_scripts/"
const CONSOLE_LOGS_PATH = "./console_logs"
const PACKAGE_JSON_PATH = __dirname+"/package.json"
const STATIC_PATH = __dirname+"/views/static/"
const TEMPLATE_PATH =__dirname+"/views/templates"
//Global variables
var api = null //contains agent export
var loaded_classes = []
var system_classes = []
var loaded_methods = {}
var target_package = ""
var system_package = ""
var no_system_package=false
var app_list = [] //apps installed on the device
var mobile_OS="N/A"
var app_env_info = {} //app env info
//Global variables - diff analysis
var current_loaded_classes = []
var new_loaded_classes = []
//Global variables - console output
var calls_console_output = ""
var hooks_console_output = ""
var heap_console_output = ""
var global_console_output = ""
var api_monitor_console_output = ""
var static_analysis_console_output = ""
//Global variables - call stack
var call_count = 0
var call_count_stack={}
var methods_hooked_and_executed = []
//app instance
const app = express();
// server instance
const server = http.createServer(app);
// socket listen
const io=socket_io(server);
//bind socket_io to app
app.set('socket_io', io);
//express post config
app.use(bodyParser.urlencoded({ extended: true, limit: '50mb' }));
app.use(bodyParser.json());
//express static path
app.use(express.static(STATIC_PATH));
//nunjucks config
nunjucks.configure(TEMPLATE_PATH, {
autoescape: true,
express: app
});
/*
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Server startup
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
async function startServer() {
try {
frida = await import('frida');
console.log("✅ Frida module loaded successfully!");
const PORT_INTERFACE = process.argv.includes('--port') ? process.argv[process.argv.indexOf('--port') + 1] : 5491;
server.listen(PORT_INTERFACE, () => {
console.log("")
console.log("_________________________________________________________")
console.log("RMS - Runtime Mobile Security")
console.log("Version: "+(require(PACKAGE_JSON_PATH).version))
console.log("by @mobilesecurity_")
console.log("Twitter Profile: https://twitter.com/mobilesecurity_")
console.log("_________________________________________________________")
console.log("")
console.log(`Running on http://127.0.0.1:${PORT_INTERFACE}/ (Press CTRL+C to quit)`);
});
io.on('connection', (socket) => {
console.log('Socket connected');
socket.on('disconnect', () => {
console.log('Socket disconnected');
});
});
} catch (err) {
console.error("💥 CRITICAL ERROR: Failed to load Frida or start server!");
console.error(err);
process.exit(1); // Exit the app if Frida can't load
}
}
/*
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Templates
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
//{{stacktrace}} placeholder is managed nodejs side
template_massive_hook_Android = `
Java.perform(function () {
var classname = "{className}";
var classmethod = "{classMethod}";
var methodsignature = "{methodSignature}";
var hookclass = Java.use(classname);
hookclass.{classMethod}.{overload}implementation = function ({args}) {
send("[Call_Stack]\\nClass: " +classname+"\\nMethod: "+methodsignature+"\\n");
var ret = this.{classMethod}({args});
var s="";
s=s+"[Hook_Stack]\\n"
s=s+"Class: "+classname+"\\n"
s=s+"Method: "+methodsignature+"\\n"
s=s+"Called by: "+Java.use('java.lang.Exception').$new().getStackTrace().toString().split(',')[1]+"\\n"
s=s+"Input: "+eval(args)+"\\n"
s=s+"Output: "+ret+"\\n"
{{stacktrace}}
send(s);
return ret;
};
});
`
template_massive_hook_iOS = `
var classname = "{className}";
var classmethod = "{classMethod}";
var methodsignature = "{methodSignature}";
try {
var hook = eval('ObjC.classes["' + classname + '"]["' + classmethod + '"]');
Interceptor.attach(hook.implementation, {
onEnter: function (args) {
send("[Call_Stack]\\nClass: " + classname + "\\nMethod: " + methodsignature + "\\n");
this.s = ""
this.s = this.s + "[Hook_Stack]\\n"
this.s = this.s + "Class: " + classname + "\\n"
this.s = this.s + "Method: " + methodsignature + "\\n"
if (classmethod.indexOf(":") !== -1) {
var params = classmethod.split(":");
params[0] = params[0].split(" ")[1];
for (var i = 0; i < params.length - 1; i++) {
try {
this.s = this.s + "Input: " + params[i] + ": " + new ObjC.Object(args[2 + i]).toString() + "\\n";
} catch (e) {
this.s = this.s + "Input: " + params[i] + ": " + args[2 + i].toString() + "\\n";
}
}
}
},
onLeave: function (retval) {
this.s = this.s + "Output: " + retval.toString() + "\\n";
{{stacktrace}}
send(this.s);
}
});
} catch (err) {
send("[!] Exception: " + err.message);
send("Not able to hook \\nClass: " + classname + "\\nMethod: " + methodsignature + "\\n");
}
`
template_hook_lab_Android = `
Java.perform(function () {
var classname = "{className}";
var classmethod = "{classMethod}";
var methodsignature = "{methodSignature}";
var hookclass = Java.use(classname);
//{methodSignature}
hookclass.{classMethod}.{overload}implementation = function ({args}) {
send("[Call_Stack]\\nClass: " +classname+"\\nMethod: "+methodsignature+"\\n");
var ret = this.{classMethod}({args});
var s="";
s=s+"[Hook_Stack]\\n"
s=s+"Class: " +classname+"\\n"
s=s+"Method: " +methodsignature+"\\n"
s=s+"Called by: "+Java.use('java.lang.Exception').$new().getStackTrace().toString().split(',')[1]+"\\n"
s=s+"Input: "+eval({args})+"\\n";
s=s+"Output: "+ret+"\\n";
//uncomment the line below to print StackTrace
//s=s+"StackTrace: "+Java.use('android.util.Log').getStackTraceString(Java.use('java.lang.Exception').$new()).replace('java.lang.Exception','') +"\\n";
send(s);
return ret;
};
});
`
template_hook_lab_iOS = `
var classname = "{className}";
var classmethod = "{classMethod}";
var methodsignature = "{methodSignature}";
try {
var hook = eval('ObjC.classes["' + classname + '"]["' + classmethod + '"]');
//{methodSignature}
Interceptor.attach(hook.implementation, {
onEnter: function (args) {
send("[Call_Stack]\\nClass: " + classname + "\\nMethod: " + methodsignature + "\\n");
this.s = ""
this.s = this.s + "[Hook_Stack]\\n"
this.s = this.s + "Class: " + classname + "\\n"
this.s = this.s + "Method: " + methodsignature + "\\n"
if (classmethod.indexOf(":") !== -1) {
var params = classmethod.split(":");
params[0] = params[0].split(" ")[1];
for (var i = 0; i < params.length - 1; i++) {
try {
this.s = this.s + "Input: " + params[i] + ": " + new ObjC.Object(args[2 + i]).toString() + "\\n";
} catch (e) {
this.s = this.s + "Input: " + params[i] + ": " + args[2 + i].toString() + "\\n";
}
}
}
},
//{methodSignature}
onLeave: function (retval) {
this.s = this.s + "Output: " + retval.toString() + "\\n";
//uncomment the lines below to replace retvalue
//retval.replace(0);
//uncomment the line below to print StackTrace
//this.s = this.s + "StackTrace: \\n" + Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\\n') + "\\n";
send(this.s);
}
});
} catch (err) {
send("[!] Exception: " + err.message);
send("Not able to hook \\nClass: " + classname + "\\nMethod: " + methodsignature + "\\n");
}
`
template_heap_search_Android = `
Java.performNow(function () {
var classname = "{className}"
var classmethod = "{classMethod}";
var methodsignature = "{methodSignature}";
Java.choose(classname, {
onMatch: function (instance) {
try
{
var returnValue;
//{methodSignature}
returnValue = instance.{classMethod}({args}); //<-- replace v[i] with the value that you want to pass
//Output
var s = "";
s=s+"[Heap_Search]\\n"
s=s + "[*] Heap Search - START\\n"
s=s + "Instance Found: " + instance.toString() + "\\n";
s=s + "Calling method: \\n";
s=s + " Class: " + classname + "\\n"
s=s + " Method: " + methodsignature + "\\n"
s=s + "-->Output: " + returnValue + "\\n";
s = s + "[*] Heap Search - END\\n"
send(s);
}
catch (err)
{
var s = "";
s=s+"[Heap_Search]\\n"
s=s + "[*] Heap Search - START\\n"
s=s + "Instance NOT Found or Exception while calling the method\\n";
s=s + " Class: " + classname + "\\n"
s=s + " Method: " + methodsignature + "\\n"
s=s + "-->Exception: " + err + "\\n"
s=s + "[*] Heap Search - END\\n"
send(s)
}
}
});
});
`
template_heap_search_iOS = `
var classname = "{className}";
var classmethod = "{classMethod}";
var methodsignature = "{methodSignature}";
ObjC.choose(ObjC.classes[classname], {
onMatch: function (instance) {
try
{
var returnValue;
//{methodSignature}
returnValue = instance[classmethod](); //<-- insert args if needed
var s=""
s=s+"[Heap_Search]\\n"
s=s + "[*] Heap Search - START\\n"
s=s+"Instance Found: " + instance.toString() + "\\n";
s=s+"Calling method: \\n";
s=s+" Class: " + classname + "\\n"
s=s+" Method: " + methodsignature + "\\n"
s=s+"-->Output: " + returnValue + "\\n";
s=s+"[*] Heap Search - END\\n"
send(s);
}catch(err)
{
var s = "";
s=s+"[Heap_Search]\\n"
s=s + "[*] Heap Search - START\\n"
s=s + "Instance NOT Found or Exception while calling the method\\n";
s=s + " Class: " + classname + "\\n"
s=s + " Method: " + methodsignature + "\\n"
s=s + "-->Exception: " + err + "\\n"
s=s + "[*] Heap Search - END\\n"
send(s)
}
},
onComplete: function () {
}
});
`
/*
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Device - TAB
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
app.get("/", async function(req, res){
const config = read_json_file(CONFIG_FILE_PATH)
let custom_scripts_Android = []
let custom_scripts_iOS = []
//#exception handling - frida crash
var frida_crash=req.query.frida_crash || "False"
var frida_crash_message=req.query.frida_crash_message
var device=null
app_list=null
//get device
try
{
const device_manager = await frida.getDeviceManager()
switch(config.device_type)
{
case "USB":
device = await frida.getUsbDevice()
break;
case "Remote":
device = await device_manager.addRemoteDevice(config.device_args.host)
break;
case "ID":
device= await device_manager.getDevice(config.device_args.id)
break;
default:
device = await frida.getUsbDevice()
break;
}
//get app list
app_list = await device.enumerateApplications()
if (app_list.length == 0)
return res.redirect('/config?error=True');
}
catch(err)
{
console.log(err)
return res.redirect('/config?error=True');
}
const device_info="name: "+device.name+" | id: "+device.id+" | mode: "+device.type
//load FRIDA custom scripts list
fs.readdirSync(CUSTOM_SCRIPTS_PATH+"Android").forEach(file => {
if (file.endsWith(".js"))
custom_scripts_Android.push(file)
})
fs.readdirSync(CUSTOM_SCRIPTS_PATH+"iOS").forEach(file => {
if (file.endsWith(".js"))
custom_scripts_iOS.push(file)
})
//sort custom_scripts alphabetically
custom_scripts_Android.sort()
custom_scripts_iOS.sort()
//load API Monitors list
const api_monitor = read_json_file(API_MONITOR_FILE_PATH)
let template = {
device_info: device_info,
app_list: app_list,
api_monitor: api_monitor,
system_package_Android: config.system_package_Android,
system_package_iOS: config.system_package_iOS,
device_mode: config.device_type,
custom_scripts_Android: custom_scripts_Android,
custom_scripts_iOS: custom_scripts_iOS,
frida_crash: frida_crash,
frida_crash_message: frida_crash_message,
no_system_package: no_system_package,
target_package: target_package,
system_package: system_package
}
res.render("device.html", template)
})
app.post("/", async function(req, res){
//output reset
reset_variables_and_output()
//check if RMS agent exist
if(!fs.existsSync(FRIDA_AGENT_PATH))
{
console.log("")
console.log("RMS agent does not exist at path: "+FRIDA_AGENT_PATH)
console.log("in order to compile it, please run the following command:")
console.log("npm install -g rms-runtime-mobile-security")
console.log("***")
console.log("For Development mode ONLY --> check the readme on Github")
console.log("You can compile the agent via the following command:")
console.log("npm install or npm run compile")
console.log("***")
console.log("")
//RMS exit - important file is missing
process.kill(process.pid, 'SIGTERM')
}
//read config file
const config = read_json_file(CONFIG_FILE_PATH)
//obtain device OS
mobile_OS = req.body.mobile_OS
//set the proper system package
if(mobile_OS=="Android")
system_package=config.system_package_Android
else
system_package=config.system_package_iOS
//set the target package
target_package = req.body.package
//Frida Gadget support
if (target_package=="re.frida.Gadget")
target_package="Gadget"
//setup the RMS run
const mode = req.body.mode
const frida_script = req.body.frida_startup_script
const api_selected = req.body.api_selected
//RMS overview - print run options
console.log()
if(target_package)
console.log("Package Name: " + target_package)
if(mode)
console.log("Mode: " + mode)
if(frida_script)
console.log("Frida Startup Script: \n" + frida_script)
else
console.log("Frida Startup Script: None")
if(api_selected)
console.log("APIs Monitors: \n" + api_selected)
else
console.log("APIs Monitors: None")
var device=null
//get device
try
{
const device_manager = await frida.getDeviceManager()
switch(config.device_type)
{
case "USB":
device = await frida.getUsbDevice()
break;
case "Remote":
device = await device_manager.addRemoteDevice(config.device_args.host)
break;
case "ID":
device= await device_manager.getDevice(config.device_args.id)
break;
default:
device = await frida.getUsbDevice()
break;
}
}
catch(err)
{
console.log(err)
return res.redirect('/config?error=True');
}
//spawn/attach the app/gadget
let session, script;
try
{
//attaching a persistent process to get enumerateLoadedClasses() result before starting the target app
//default process are com.android.systemui/SpringBoard
session = await device.attach(system_package)
const frida_agent = await fs.readFileSync(FRIDA_AGENT_PATH, 'utf8');
script = await session.createScript(frida_agent)
await script.load()
api = await script.exports
system_classes = await api.loadclasses()
//sort list alphabetically
system_classes.sort()
}
catch(err)
{
console.log("Exception: "+err)
if (system_classes.length==0)
no_system_package=true
if (target_package!="Gadget")
console.log(system_package+" is NOT available on your device or a wrong OS has been selected. For a better RE experience, change it via the Config TAB!");
}
session = null
pid=null
try
{
if (mode == "Spawn" && target_package!="Gadget")
{
pid= await device.spawn([target_package])
session = await device.attach(pid)
console.log('[*] Process Spawned')
}
if (mode == "Attach" || target_package=="Gadget")
{
// On iOS and Android devices "attach" is performed via process.pid instead of package identifier or name
// The previous approach did not work with a specific package name.
var target_pid;
if(target_package!="Gadget")
{
app_list.forEach(function(p) {
if(p.identifier==target_package) {
target_package = p.name;
target_pid = p.pid;
if (target_package == 0) {
console.log("The application does not seem to be running... Please launch before attaching to it!");
}
}
});
}
session = await device.attach(target_pid)
console.log('[*] Process Attached')
}
const frida_agent = await fs.readFileSync(FRIDA_AGENT_PATH, 'utf8');
script = await session.createScript(frida_agent)
//crash handling
device.processCrashed.connect(onProcessCrashed);
session.detached.connect(onSessionDetached);
//onMessage
script.message.connect(onMessage);
await script.load()
//API export
api = script.exports
if (mode == "Spawn" && target_package!="Gadget")
device.resume(pid)
//loading FRIDA startup script if selected by the user
if (frida_script)
await api.loadcustomfridascript(frida_script)
//loading APIs Monitors if selected by the user
if(api_selected)
{
//load API Monitors list
const api_monitor = read_json_file(API_MONITOR_FILE_PATH)
var api_to_hook=[]
api_monitor.forEach(function(e) {
if(api_selected.includes(e.Category))
api_to_hook.push(e)
});
//load APIs monitors
try
{
await api.apimonitor(api_to_hook)
}
catch(err)
{
console.log("Exception: "+err)
}
}
}//end try
catch(err)
{
console.log("Exception: "+err)
return res.redirect('/?frida_crash=True&frida_crash_message='+err);
}
//automatically redirect the user to the dump classes and methods tab
let template = {
mobile_OS: mobile_OS,
target_package: target_package,
loaded_classes: loaded_classes,
loaded_methods: loaded_methods,
system_package: system_package,
no_system_package: no_system_package
}
res.render("dump.html",template)
})
function onProcessCrashed(crash) {
console.log('[*] onProcessCrashed() crash:', crash);
console.log(crash.report);
}
function onSessionDetached(reason, crash) {
console.log('[*] onDetached() reason:', reason, 'crash:', crash);
}
/*
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Static Analysis - TAB (iOS only)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
app.get("/static_analysis", async function(req, res){
//obtain static analysis script path
static_analysis_script_path=CUSTOM_SCRIPTS_PATH+ mobile_OS +"/static_analysis.js"
//read the script
static_analysis_script = fs.readFileSync(static_analysis_script_path, 'utf8')
//run it via the loadcustomfridascript api
try
{
await api.loadcustomfridascript(static_analysis_script)
}
catch(err)
{
console.log(err)
}
let template = {
mobile_OS: mobile_OS,
static_analysis_console_output: static_analysis_console_output,
target_package: target_package,
system_package: system_package,
no_system_package: no_system_package
}
res.render("static_analysis.html", template);
})
/*
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dump Classes and Methods - TAB
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
app.get("/dump", async function(req, res){
//# check what the user is triyng to do
const choice = req.query.choice
if (choice == 1){
// --> Dump Loaded Classes (w/o filters)
//clean up the array
loaded_classes = []
loaded_methods = []
//check if the user is trying to filter loaded classes
filter = req.query.filter
//Checking options
regex=0
case_sensitive=0
whole_world=0
if(req.query.regex==1) regex=1
if(req.query.case==1) case_sensitive=1
if(req.query.whole==1) whole_world=1
if (filter)
{
hooked_classes = await api.loadclasseswithfilter(filter,
regex,
case_sensitive,
whole_world)
loaded_classes = hooked_classes
}
else
{
loaded_classes = await api.loadclasses()
//Checking current loaded classes
/*
perform --> loaded classes -
system classes =
______________________
current_loaded_classes
*/
loaded_classes=loaded_classes.filter(function(x)
{
return system_classes.indexOf(x) < 0;
});
}
//sort loaded_classes alphabetically
loaded_classes.sort()
//console.log(loaded_classes)
}
if (choice == 2){
// --> Dump all methods [Loaded Classes]
// NOTE: Load methods for more than 500 classes can crash the app
try
{
loaded_methods = await api.loadmethods(loaded_classes)
//console.log(loaded_methods)
}
catch (err) {
console.log("Exception: "+err)
msg="FRIDA crashed while loading methods for one or more classes selected. Try to exclude them from your search!"
console.log(msg)
return res.redirect('/?frida_crash=True&frida_crash_message='+err);
}
}
if (choice == 3)
{
//--> Hook all loaded classes and methods
current_template=""
if (mobile_OS=="Android")
current_template=template_massive_hook_Android
else
current_template=template_massive_hook_iOS
const stacktrace = req.query.stacktrace
if (stacktrace == "yes")
{
if (mobile_OS=="Android")
current_template=current_template.replace("{{stacktrace}}", "s=s+\"StackTrace: \"+Java.use('android.util.Log').getStackTraceString(Java.use('java.lang.Exception').$new()).replace('java.lang.Exception','') +\"\\n\";")
else
current_template=current_template.replace("{{stacktrace}}", "this.s=this.s+\"StackTrace: \\n\"+Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\\n') +\"\\n\";")
}
else
current_template=current_template.replace("{{stacktrace}}", "")
try
{
await api.hookclassesandmethods(loaded_classes,
loaded_methods,
current_template)
}
catch(err)
{
console.log("Exception: "+err)
msg="FRIDA crashed while hooking methods for one or more classes selected. Try to exclude them from your search!"
console.log(msg)
return res.redirect('/?frida_crash=True&frida_crash_message='+err);
}
//redirect the user to the console output
return res.redirect('/console_output');
}
let template = {
mobile_OS: mobile_OS,
target_package: target_package,
loaded_classes: loaded_classes,
loaded_methods: loaded_methods,
system_package: system_package,
methods_hooked_and_executed: methods_hooked_and_executed
}
res.render("dump.html",template)
})
app.post("/dump", async function(req, res){
//tohook contains class (index) selected by the user (hooking purposes)
array_to_hook = req.body.tohook
if(!Array.isArray(array_to_hook))
array_to_hook=[array_to_hook]
if (array_to_hook)
{
hooked_classes = []
array_to_hook.forEach(function(index)
{
//hooked classes
hooked_classes.push(loaded_classes[Number(index)])
})
loaded_classes = hooked_classes
}
let template = {
mobile_OS: mobile_OS,
target_package: target_package,
loaded_classes: loaded_classes,
loaded_methods: loaded_methods,
system_package: system_package,
methods_hooked_and_executed: methods_hooked_and_executed
}
res.render("dump.html",template)
})
/*
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Diff Classess - TAB
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
app.get("/diff_classes", async function(req, res){
//# check what the user is triyng to do
const choice = req.query.choice
if (choice == 1)
{
//Checking current loaded classes
/*
perform --> loaded classes -
system classes =
______________________
current_loaded_classes
*/
current_loaded_classes = (await api.loadclasses()).filter(
function(x)
{
return system_classes.indexOf(x) < 0;
});
//sort list alphabetically
current_loaded_classes.sort()
}
if (choice == 2)
{
//Checking NEW loaded classes
/*
perform --> new loaded classes -
old loaded classes -
system classes =
_____________________
new_loaded_classes
*/
new_loaded_classes = (await api.loadclasses()).filter(
function(x)
{
return current_loaded_classes.indexOf(x) < 0;
}
);
new_loaded_classes = new_loaded_classes.filter(
function(x)
{
return system_classes.indexOf(x) < 0;
}
);
//sort list alphabetically
new_loaded_classes.sort()
}
let template = {
mobile_OS: mobile_OS,
current_loaded_classes: current_loaded_classes,
new_loaded_classes: new_loaded_classes,
target_package: target_package,
system_package: system_package
}
res.render("diff_classes.html",template)
})
/*
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Hook LAB - TAB
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
function get_hook_lab_template(mobile_OS){
if (mobile_OS=="Android")
return template_hook_lab_Android
else
return template_hook_lab_iOS
}
app.get("/hook_lab", async function(req, res){
//check if methods are loaded or not
if (loaded_methods === undefined || loaded_methods.length == 0)
{
try{
loaded_methods = await api.loadmethods(loaded_classes)
return res.redirect("/hook_lab")
}
catch(err){
console.log("Exception: "+err)
const msg="FRIDA crashed while loading methods for one or more classes selected. Try to exclude them from your search!"
console.log(msg)
return res.redirect('/?frida_crash=True&frida_crash_message='+err);
}
}
var hook_template = ""
var selected_class = ""
//class_index contains the index of the loaded class selected by the user
class_index = req.query.class_index
if(class_index)
{
//get methods of the selected class
selected_class = loaded_classes[class_index]
//method_index contains the index of the loaded method selected by the user
method_index = req.query.method_index
//Only class selected - load heap search template for all the methods
if (!method_index)
{
//hook template generation
hook_template = await api.generatehooktemplate(
[selected_class],
loaded_methods,
get_hook_lab_template(mobile_OS)
)
}
//class and method selected - load heap search template for selected method only
else
{
var selected_method={}
//get method of the selected class
selected_method[selected_class] =
[(loaded_methods[selected_class])[method_index]]
//hook template generation
hook_template = await api.generatehooktemplate(
[selected_class],
selected_method,
get_hook_lab_template(mobile_OS)
)
}
}
//print hook template
let template = {
mobile_OS: mobile_OS,
target_package: target_package,
system_package: system_package,
no_system_package: no_system_package,
loaded_classes: loaded_classes,
loaded_methods: loaded_methods,
selected_class: selected_class,
methods_hooked_and_executed: methods_hooked_and_executed,
hook_template_str: hook_template
}
res.render("hook_lab.html",template);
})
/*
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Heap Search - TAB
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
function get_heap_search_template(mobile_OS){
if (mobile_OS=="Android")
return template_heap_search_Android
else
return template_heap_search_iOS
}
app.get("/heap_search", async function(req, res){
//check if methods are loaded or not
if (loaded_methods === undefined || loaded_methods.length == 0)
{
try{
loaded_methods = await api.loadmethods(loaded_classes)
return res.redirect("/heap_search")
}
catch(err){
console.log("Exception: "+err)
const msg="FRIDA crashed while loading methods for one or more classes selected. Try to exclude them from your search!"
console.log(msg)
return redirect('/?frida_crash=True&frida_crash_message='+err);
}
}
var heap_template = ""
var selected_class = ""
//lass_index contains the index of the loaded class selected by the user
class_index = req.query.class_index
if(class_index)
{
//get methods of the selected class
selected_class = loaded_classes[class_index]
//method_index contains the index of the loaded method selected by the user
method_index = req.query.method_index
//Only class selected - load heap search template for all the methods
if (!method_index)
{
//heap template generation
heap_template = await api.heapsearchtemplate(
[selected_class],
loaded_methods,
get_heap_search_template(mobile_OS)
)
}
//class and method selected - load heap search template for selected method only
else
{
var selected_method={}
//get method of the selected class
selected_method[selected_class] =
[(loaded_methods[selected_class])[method_index]]
//heap template generation
heap_template = await api.heapsearchtemplate(
[selected_class],
selected_method,
get_heap_search_template(mobile_OS)
)
}
}
//print hook template
let template = {
mobile_OS: mobile_OS,
target_package: target_package,
system_package: system_package,
no_system_package: no_system_package,
loaded_classes: loaded_classes,
loaded_methods: loaded_methods,
selected_class: selected_class,
methods_hooked_and_executed: methods_hooked_and_executed,
heap_template_str: heap_template,
heap_search_console_output_str: heap_console_output
}
res.render("heap_search.html", template);
})
/*
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
API Monitor - TAB
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
app.get("/api_monitor", async function(req, res){
const api_monitor = read_json_file(API_MONITOR_FILE_PATH)
let template = {
mobile_OS: mobile_OS,
target_package: target_package,
system_package: system_package,
no_system_package: no_system_package,
api_monitor: api_monitor,
api_monitor_console_output_str: api_monitor_console_output
}
res.render("api_monitor.html",template);
})
app.post("/api_monitor", async function(req, res){
const api_monitor = read_json_file(API_MONITOR_FILE_PATH)
const api_selected = req.body.api_selected
var api_to_hook=[]
api_monitor.forEach(function(e) {
if(api_selected.includes(e.Category))
api_to_hook.push(e)
});
try
{
await api.apimonitor(api_to_hook)
}
catch(err)
{
console.log("Exception: "+err)
return res.redirect('/?frida_crash=True&frida_crash_message='+err);
}
let template = {
mobile_OS: mobile_OS,
target_package: target_package,
system_package: system_package,
no_system_package: no_system_package,
api_monitor: api_monitor,
api_monitor_console_output_str: api_monitor_console_output
}
res.render("api_monitor.html",template);
})
/*
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
File Manager - TAB
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
app.get("/file_manager", async function(req, res){
const path=req.query.path
const download=req.query.download
var files_at_path=""
var file=""
//check is app_env_info is not loaded yet
if (Object.keys(app_env_info).length === 0)
app_env_info=await api.getappenvinfo()
if(path)
{
files_at_path=await api.listfilesatpath(path)
//console.log(files_at_path)
}
if(download)
{
file=await api.downloadfileatpath(download)
/*
if(file)
{
file=''.join(map(chr, (file)["data"]))
filename=os.path.basename(os.path.normpath(download))
//console.log(filename)
return Response(file,
headers={
"Content-disposition":
"attachment; filename="+filename}
)
}
*/
}
let template = {
mobile_OS: mobile_OS,
target_package: target_package,
system_package: system_package,
no_system_package: no_system_package,
env: app_env_info,
files_at_path: files_at_path,
currentPath: path,
BETA: BETA
}
res.render("file_manager.html",template);
})
app.post("/file_manager", async function(req, res){
})
/*
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Load Frida Script - TAB
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
app.get("/load_frida_script", async function(req, res){
//Load frida custom scripts inside "custom_scripts" folder
var custom_scripts = []
fs.readdirSync(CUSTOM_SCRIPTS_PATH+mobile_OS).forEach(file =>
{
if (file.endsWith(".js"))
custom_scripts.push(file)
}
)
//sort custom_scripts alphabetically
custom_scripts.sort()
//open the custom script selected by the user
const cs_name = req.query.cs
var cs_file=""
//check if a custom script has been selected
if(cs_name){
cs_path=CUSTOM_SCRIPTS_PATH+mobile_OS+"/"+ cs_name
cs_file=fs.readFileSync(cs_path, 'utf8')
}
let template = {
mobile_OS: mobile_OS,
target_package: target_package,
system_package: system_package,
custom_scripts: custom_scripts,
custom_script_loaded: cs_file,
no_system_package: no_system_package
}
res.render("load_frida_script.html",template);
})
app.post("/load_frida_script", async function(req, res){
script = req.body.frida_custom_script
try
{
await api.loadcustomfridascript(script)
}
catch(err){
console.log("Exception: "+err)
}
//auto redirect the user to the console output page
return res.redirect('/console_output');
})
/*
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Console Output - TAB
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
app.get("/console_output", async function(req, res){
let template = {
mobile_OS: mobile_OS,
called_console_output_str: calls_console_output,
hooked_console_output_str: hooks_console_output,
global_console_output_str: global_console_output,
target_package: target_package,
system_package: system_package,
no_system_package: no_system_package
}
res.render("console_output.html",template)
})
/*
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
API - Print Console logs to a File
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
app.get("/save_console_logs", async function(req, res){
try
{
//check if console_logs exists
if (!fs.existsSync(CONSOLE_LOGS_PATH))
fs.mkdirSync("console_logs")
//create new directory for current logs package_timestamp
const dt = datetime.create().format('Ymd-HMS')
out_path=CONSOLE_LOGS_PATH+"/"+target_package+"_"+dt
fs.mkdirSync(out_path)
//save calls_console_output
fs.writeFile(out_path+"/calls_console_output.txt",
calls_console_output,
function(err){
if(err)
console.log(err);
console.log("calls_console_output.txt saved");
});
//save hooks_console_output
fs.writeFile(out_path+"/hooks_console_output.txt",
hooks_console_output,
function(err)
{
if(err)
console.log(err);
console.log("hooks_console_output.txt saved");
});
//save global_console_output
fs.writeFile(out_path+"/global_console_output.txt",
global_console_output,
function(err)
{
if(err)
console.log(err);
console.log("global_console_output.txt saved");
});
//save api_monitor_console_output - not available on iOS
if(mobile_OS=="Android")
{
fs.writeFile(out_path+"/api_monitor_console_output.txt",
api_monitor_console_output,
function(err)
{
if(err)
console.log(err);
console.log("api_monitor_console_output.txt saved");
});
}
out_path=out_path.replace("./",process.cwd()+"/")
res.send("print_done - "+out_path)
}
catch(err){
res.send("print_error: "+err)
}
})
/*
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
API - Reset Console Output
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
app.get("/reset_console_logs", async function(req, res){
calls_console_output = ""
hooks_console_output = ""
heap_console_output = ""
global_console_output = ""
api_monitor_console_output = ""
static_analysis_console_output = ""
call_count = 0
call_count_stack = {}
methods_hooked_and_executed = []
redirect_url = req.query.redirect
//auto redirect the user to the console output page
return res.redirect(redirect_url);
})
/*
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Config File - TAB
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
app.all("/config", async function(req, res){
/*
|POST!
*/
if (req.method == "POST")
{
//read new values
const device_type = req.body.device_type
const system_package_Android = req.body.system_package_Android.trim()
const system_package_iOS = req.body.system_package_iOS.trim()
let device_arg_host = req.body.host_value
let device_arg_id = req.body.id_value
if(!device_arg_host) device_arg_host=""
if(!device_arg_id) device_arg_id=""
device_args={
host:device_arg_host,
id:device_arg_id
}
new_config = {}
new_config.device_type=device_type
new_config.system_package_Android=system_package_Android
new_config.system_package_iOS=system_package_iOS
new_config.device_args=device_args
console.log("NEW CONFIG")
console.log(new_config)
//write new config to config.js
fs.writeFileSync(CONFIG_FILE_PATH,
JSON.stringify(new_config,null,4));
}
/*
|GET!
*/
//read config file
const config = read_json_file(CONFIG_FILE_PATH)
error = false
if (req.query.error)
error = true
let template = {
system_package_Android: config.system_package_Android,
system_package_iOS: config.system_package_iOS,
device_type_selected: config.device_type,
device_type_options: FRIDA_DEVICE_OPTIONS,
device_args: config.device_args,
device_args_options: FRIDA_DEVICE_ARGS_OPTIONS,
error: error
}
res.render("config.html", template);
})
/*
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
API - eval frida script and redirect
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
app.post("/eval_script_and_redirect", async function(req, res){
script = req.body.frida_custom_script
redirect_url = req.body.redirect
console.log(script)
console.log(redirect_url)
try
{
await api.loadcustomfridascript(script)
}
catch(err){
console.log("Exception: "+err)
}
//auto redirect the user to the console output page
return res.redirect(redirect_url);
})
/*
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
API - get frida custom script as text (Device Page)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
app.get("/get_frida_custom_script", async function(req, res){
const mobile_os_get = req.query.os;
const custom_script_get = req.query.cs;
const cs_file_path = CUSTOM_SCRIPTS_PATH+mobile_os_get+"/"+custom_script_get
let custom_script = ""
if(mobile_os_get && custom_script_get){
custom_script=fs.readFileSync(cs_file_path, 'utf8')
}
res.send(custom_script)
});
/*
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
on_message stuff
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
function onMessage(message, data) {
//TODO check what's wrong with loadclasses
/*
if(message.type!="error"){
console.log('[*] onMessage() message:', message, 'data:', data);
}
*/
if (message.type == 'send'){
if(message.payload.includes("[Call_Stack]"))
log_handler("call_stack",message.payload)
if(message.payload.includes("[Hook_Stack]"))
log_handler("hook_stack",message.payload)
if(message.payload.includes("[Heap_Search]"))
log_handler("heap_search",message.payload)
if(message.payload.includes("[API_Monitor]"))
log_handler("api_monitor",message.payload)
if(message.payload.includes("[Static_Analysis]"))
log_handler("static_analysis",message.payload)
if(!(message.payload.includes("[Call_Stack]")) &&
!(message.payload.includes("[Hook_Stack]")) &&
!(message.payload.includes("[Heap_Search]")) &&
!(message.payload.includes("[API_Monitor]")) &&
!(message.payload.includes("[Static_Analysis]"))
)
log_handler("global_stack",message.payload)
}
}
function log_handler(level, text){
if(!text) return //needed?
switch (level)
{
case "call_stack":
//clean up the string
text=text.replace("[Call_Stack]\n","")
//method hooked has been executed by the app
var new_m_executed=text //text contains Class and Method info
//remove duplicates
if (!methods_hooked_and_executed.includes(new_m_executed))
methods_hooked_and_executed.push(new_m_executed)
//add the current call (method) to the call stack
call_count_stack[new_m_executed]=call_count
//creating string for the console output by adding INDEX info
text = "-->INDEX: [" + call_count + "]\n" + text
calls_console_output = calls_console_output + "\n" + text
//increase the counter
call_count += 1
io.emit(
'call_stack',
{
'data': "\n"+text,
'level': level
},
namespace='/console'
)
break;
case "hook_stack":
//clean up the string
text=text.replace("[Hook_Stack]\n","")
//obtain current method info - first 2 lines contain Class and Method info
var current_method=(text.split("\n").splice(0,2)).join('\n')+'\n'
//check the call order by looking at the stack call
var out_index=-1 //default value if for some reasons current method is not in the stack
try
{
out_index=call_count_stack[current_method]
}
catch(err)
{
console.log("Not able to assign: \n"+current_method+"to its index")
}
//assign the correct index (stack call) to the current hooked method and relative info (IN/OUT)
text="INFO for INDEX: ["+out_index+"]\n"+text
hooks_console_output = hooks_console_output + "\n" + text
io.emit(
'hook_stack',
{
'data': "\n"+text,
'level': level
},
namespace='/console'
)
break;
case "heap_search":
text=text.replace("[Heap_Search]\n","")
heap_console_output = heap_console_output + "\n" + text
io.emit(
'heap_search',
{
'data': "\n"+text,
'level': level
},
namespace='/console'
)
break;
case "api_monitor":
api_monitor_console_output = api_monitor_console_output + "\n" + text
io.emit(
'api_monitor',
{
'data': "\n"+text,
'level': level
},
namespace='/console'
)
break;
case "static_analysis":
text=text.replace("[Static_Analysis]\n","")
static_analysis_console_output=text
default:
break;
}
//always executed
global_console_output = global_console_output + "\n" + text
io.emit(
'global_console',
{
'data': "\n"+text,
'level': level
},
namespace='/console'
)
//print text
console.log(text)
}
/*
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Supplementary functions
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
function read_json_file(path) {
return JSON.parse(fs.readFileSync(path, 'utf8'));
}
function reset_variables_and_output(){
mobile_OS="N/A"
//output reset
calls_console_output = ""
hooks_console_output = ""
heap_console_output = ""
global_console_output = ""
api_monitor_console_output = ""
static_analysis_console_output = ""
// call stack
call_count = 0
call_count_stack = {}
methods_hooked_and_executed = []
//variable reset
loaded_classes = []
system_classes = []
loaded_methods = {}
//file manager
app_env_info = {}
//diff classes variables
current_loaded_classes = []
new_loaded_classes = []
//package reset
target_package=""
system_package=""
//error reset
no_system_package=false
}
startServer();