// global
var fs = require("fs");
var Chalk = require("chalk");
var ut = require("./Utils.js");
const CLASS = require("./CoreClass.js");
var CONST = require("./CoreConst.js");
var OPCODE = require("./Opcode.js").OPCODE;
const AnalysisHelper = require("./AnalysisHelper.js");
const Event = require("./Event.js").Event;
const Logger = require("./Logger.js")();
var Parser = require("./SmaliParser.js");
const Accessor = require("./AccessFlags");
const ModelPackage = require('./ModelPackage');
const AnalyzerDatabase = require('./AnalyzerDatabase');
var SmaliParser = new Parser();
var DataModel = {
class: new CLASS.Class(),
field: new CLASS.Field(),
method: new CLASS.Method(),
call: new CLASS.Call(),
modifier: new Accessor.AccessFlags(),
objectType: new CLASS.ObjectType(),
basicType: new CLASS.BasicType()
};
var STATS = {
idxMethod: 0,
idxClass: 0,
idxField: 0,
instrCtr: 0,
methodCalls: 0,
fieldCalls: 0
};
function resolveInheritedField(fieldRef, parentClass){
for(let i in parentClass.fields){
if(parentClass.fields[i].name===fieldRef.name){
if(parentClass.fields[i].tags.indexOf('missing')>-1){
return parentClass.fields[i];
}
if(parentClass.fields[i].modifiers.private == false){
parentClass.fields[i].declaringClass = parentClass.fields[i].enclosingClass;
parentClass.fields[i].enclosingClass = parentClass;
return parentClass.fields[i];
}
}
}
if(parentClass.extends instanceof CLASS.Class){
return resolveInheritedField(fieldRef, parentClass.extends);
}else
return null;
}
function resolveInheritedMethod(methodRef, parentClass){
for(let i in parentClass.methods){
if(parentClass.methods[i].name===methodRef.name){
if(parentClass.methods[i].tags.indexOf('missing')>-1){
return parentClass.methods[i];
}
if(parentClass.methods[i].modifiers.private == false){
parentClass.methods[i].declaringClass = parentClass.methods[i].enclosingClass;
parentClass.methods[i].enclosingClass = parentClass;
return parentClass.methods[i];
}
}
}
if(parentClass.extends instanceof CLASS.Class){
return resolveInheritedMethod(methodRef, parentClass.extends);
}else
return null;
}
/**
*
* @param {String} fqcn FQCN of the missing class
* @param {AnalyzerDatabase} internalDB an instance of the internal DB
*/
function createMissingClass(fqcn,internalDB){
// create a class instance from the FQCN value
let missingCls = SmaliParser.class("L"+fqcn+" ");
let pkg = null;
// tag the class instance "missing"
missingCls.setupMissingTag();
// update the internal DB
internalDB.classes.setEntry(fqcn, missingCls);
internalDB.missing.insert(missingCls);
// update package
if(missingCls.getPackage() !== null){
pkg = internalDB.packages.getEntry(pkg);
if(!(pkg instanceof ModelPackage)){
pkg = new ModelPackage(missingCls.getPackage());
internalDB.packages.setEntry(pkg.name,pkg);
}
missingCls.setPackage(pkg);
pkg.childAppend(missingCls);
}
return missingCls;
}
function createMissingField(fieldReference, enclosingClass, internalDB, modifiers=Accessor.PUBLIC){
let missingField = fieldReference.toField();
missingField.setupMissingTag();
missingField.enclosingClass = enclosingClass;
missingField.modifiers = new Accessor.AccessFlags(modifiers);
enclosingClass.fields[missingField.signature()] = missingField;
internalDB.fields.setEntry(missingField.signature(), missingField);
internalDB.missing.insert(missingField);
return missingField;
}
function createMissingMethod(methodRef, enclosingClass, internalDB, modifiers=Accessor.PUBLIC){
let missingMeth = methodRef.toMethod();
//console.log(enclosingClass.name,missingMeth);
missingMeth.setupMissingTag();
missingMeth.enclosingClass = enclosingClass;
missingMeth.modifiers = new Accessor.AccessFlags(modifiers);
enclosingClass.methods[missingMeth.signature()] = missingMeth;
internalDB.methods.setEntry(missingMeth.signature(), missingMeth);
internalDB.missing.insert(missingMeth);
return missingMeth;
}
var Resolver = {
type: function(db, fqcn){
if(fqcn instanceof CLASS.Class){
if(db.classes.hasEntry(fqcn.fqcn)===true)
return db.classes.getEntry(fqcn.fqcn);
}else{
if(db.classes.hasEntry(fqcn)===true)
return db.classes.getEntry(fqcn);
}
// unresolvable class are created as classic Class node but are tagged "MISSING"
return createMissingClass(fqcn, db);
},
field: function(db, fieldRef){
let field = db.fields.getEntry(fieldRef.signature());
if(field instanceof CLASS.Field){
return field;
}
// if the field is not indexed, its enclosingClass is explored
let cls=db.classes.getEntry(fieldRef.fqcn);
// if enclosingClass not exists, create it
if(cls == null){
cls = createMissingClass(fieldRef.fqcn, db);
return createMissingField( fieldRef, cls, db);
//field = createMissingField(field, cls, db);
}
// MissingReference type is deprecated, so this case should never been trigged
if(cls instanceof CLASS.MissingReference){
Logger.debug("MissingReference detected");
}
field = cls.fields[fieldRef.signature()];
if(field instanceof CLASS.Field){
return field;
}
// 2. else, if the class has super class, search inherit field
if(cls.extends !== null){
field = resolveInheritedField(fieldRef, cls.extends);
if(field instanceof CLASS.Field){
cls.addInheritedField(fieldRef, field);
db.fields.setEntry(fieldRef, field);
return field;
}
}
// Finally if reference is unsolvable, the a mock field is created and tagged "missing"
return createMissingField( fieldRef, cls, db);
},
method: function(db, methRef, isStaticCall){
let meth = db.methods.getEntry(methRef.signature());
// 1. search into indexed method
if(meth instanceof CLASS.Method){
return meth;
}
// 2. else, search into inherited method
let cls=db.classes.getEntry(methRef.fqcn);
let signature = methRef.signature();
if(cls == null){
cls = createMissingClass(methRef.fqcn, db);
return createMissingMethod(methRef, cls, db, {
public: true,
static: isStaticCall
});
}
// 2. else, search into inherited method
if(cls instanceof CLASS.Class){
if(cls.extends instanceof CLASS.Class){
meth = resolveInheritedMethod(methRef, cls.extends);
if(meth instanceof CLASS.Method){
cls.addInheritedMethod(methRef, meth);
db.methods.setEntry(methRef, meth);
return meth;
}
}
}
// 4. else, mock missing method and class
return createMissingMethod(methRef, cls, db, {
public: true,
static: isStaticCall
});
}
};
/**
* To analyze each instruction and resolve symbols
*
* @param {Method} method The method to analyse
* @param {Object} data The database to use when resolving
* @param {Object} stats The statistics counters
* @function
*/
function mapInstructionFrom(method, data, stats){
let bb = null, instruct = null, obj = null, x = null, success=false, stmt=null, tmp=null, t=0,t1=0;
if(! method instanceof CLASS.Method){
Logger.error("[!] mapping failed : method provided is not an instance of Method.");
}
for(let i in method.instr){
bb = method.instr[i];
bb._parent = method;
// get basic blocks
if(bb.hasCatchStatement()){
stmt = bb.getCatchStatements();
for(let j=0; j<stmt.length; j++){
if(stmt[j].getException() != null){
stmt[j].setException( Resolver.type(data, stmt[j].getException().name));
}
stmt[j].setTryStart( method.getTryStartBlock( stmt[j].getTryStart()));
stmt[j].setTryEnd( method.getTryEndBlock( stmt[j].getTryEnd()));
if((stmt[j].getTarget() instanceof CLASS.BasicBlock) == false){
t = method.getCatchBlock( stmt[j].getTarget());
if(t !==null)
stmt[j].setTarget(t);
else{
Logger.error("Target catch block not found");
console.log( stmt[j].getTarget());
}
}
}
}
for(let j in bb.stack){
instruct = bb.stack[j];
instruct.line = bb.line;
instruct._parent = bb;
stats.instrCtr++;
if(instruct.isNOP()) continue;
success = false;
if(instruct.isDoingCall()){
if(instruct.right.special){
// ignore
continue;
}
instruct.right = Resolver.method(data, instruct.right, instruct.isStaticCall());
//instruct.right._callers.push(method);
instruct.right.addCaller(method);
tmp = new CLASS.Call({
caller: method,
calleed: instruct.right, //obj,
instr: instruct});
data.call.insert(tmp);
stats.methodCalls++;
if(method._useClass[instruct.right.fqcn] == undefined){
method._useClass[instruct.right.fqcn] = [];
method._useClassCtr++;
}
if(method._useMethod[instruct.right.signature()] == undefined){
method._useMethod[instruct.right.signature()] = [];
method._useMethodCtr++;
}
method._useClass[instruct.right.fqcn].push(instruct.right.enclosingClass);
//method._useMethod[instruct.right.signature()].push(instruct.right);
method._useMethod[instruct.right.signature()].push({
bb: i,
instr: j
});
success = true;
}
else if(instruct.isCallingField()){
if(instruct.right == null){
Logger.debug("[SAST] Call : method name is null");
}
// Never returns NULL
// if field not exists, return MissingReference object
instruct.right = Resolver.field(data, instruct.right);
/*
if(instruct.opcode.type==CONST.INSTR_TYPE.GETTER){
console.log(instruct.right);
}
*/
//instruct.right = obj;
if(instruct.right === undefined || instruct.right._callers === undefined){
Logger.debug("[SAST] Call : method cannot be resolved : ", instruct);
}
if(instruct.isSetter()){
instruct.right.addSetter(method);
}else{
instruct.right.addGetter(method);
}
instruct.right._callers.push(method);
data.call.insert(new CLASS.Call({
caller: method,
calleed: instruct.right,
instr: instruct
}));
stats.fieldCalls++;
if(method._useClass[instruct.right.fqcn] == undefined){
method._useClass[instruct.right.fqcn] = [];
method._useClassCtr++;
}
if(method._useField[instruct.right.signature()] == undefined){
method._useField[instruct.right.signature()] = [];
method._useFieldCtr++;
}
method._useClass[instruct.right.fqcn].push(instruct.right.enclosingClass);
method._useField[instruct.right.signature()].push(instruct.right);
success = true;
}
else if(instruct.isUsingString()){
// add USAGE: NEW/READ/WRITE
data.strings.insert(new CLASS.StringValue({
src: method,
instr: instruct,
value: instruct.right._value }));
success=true;
}
// Resolve Type reference
else if(instruct.isReferencingType()){
// Never returns NULL
// if type not exists, return MissingReference object
if(instruct.right instanceof CLASS.ObjectType){
obj = Resolver.type(data, instruct.right.name);
obj._callers.push(method);
instruct.right = obj;
data.call.insert(new CLASS.Call({
caller:method,
calleed:obj,
instr:instruct}));
if(method._useClass[obj.name] == undefined)
method._useClass[obj.name] = [];
//method._useClass[obj._hashcode] = obj;
method._useClass[obj.name].push(instruct);
}
success = true;
}
else
continue;
if(!success){
data.parseErrors.insert(instruct);
}
}
}
}
/*
make map by linking object :
-> resolve FQCN
-> resolve method called
and create additional index in the DB
*/
function MakeMap(data,absoluteDB){
Logger.raw("\n[*] Start object mapping ...\n------------------------------------------");
let step = data.classes.size(), /*data.classesCtr,*/ g=0;
let overrided = [];
//let updateLogs = [];
/*
let c = 0;
for(let i in data.classes)c++;
console.log(Chalk.bold.red("Classes in DB : "+c));
*/
// merge Absolute DB and Temp DB
// if a class has been already analyzed its data will be updated
data.classes.map((k,v)=>{
// add class to the absoluteDb if missing
if(absoluteDB.classes.hasEntry(k) == false){
absoluteDB.classes.setEntry(k, v);
}else{
Logger.debug("[SAST] DB merge > class overrided [ ",k," ]");
overrided.push(k);
//absoluteDB.classes.getEntry(k).update(v);
}
});
// link class with its fields and methods
// for(let i in data.classes)
data.classes.map((k,v)=>{
// make sure we manipulate freshly added class
cls = absoluteDB.classes.getEntry(k);
// is TRUE if classes are already existing in AbsoluteDB and they are defined also into TempDB
let override = (overrided.indexOf(k)>-1);
let ext = null, greater=null, smaller=null, requireRemap=false, clsSuper=null;
// the current class is already defined into AbsoluteDB,
// so, we check if we need to update superclass of classes already existing into AbsoluteDB before mapping
if(override){
// the v.extends is the string not a Class instance
// we get the reference to the superclass from the freshly added class
ext = v.getSuperClass();
try {
// For a given class from TempDB, we check if the reference to the superclass
// from the TempDB's class is the same in AbsoluteDB's class.
// Else it means the TempDB's class inherit from another class which directly
// or indirectly inherit of the superclass f AbsoluteDB's class
if(ext != null && cls.hasSuperClass()){
if((cls.getSuperClass() instanceof CLASS.Class)
&& (ext!=cls.getSuperClass().getName())){
cls.updateSuper(Resolver.type(absoluteDB, ext));
requireRemap = true;
}
}
}
catch(ex) {
Logger.error(ex);
}
}
// resolve super classes
else if(cls.hasSuperClass()){
// if (!(cls.getSuperClass() instanceof CLASS.Class)){
if(typeof cls.getSuperClass() === "string"){
cls.extends = Resolver.type(absoluteDB, cls.getSuperClass());
//cls.updateSuper( Resolver.type(absoluteDB, cls.getSuperClass()));
}
}
// map interfaces
if(override){
// here v.extends is the string not a Class instance
ext = v.getInterfaces();
if(ext.length != cls.getInterfaces().length){
cls.removeAllInterfaces();
for(let i=0; i<ext.length; i++){
cls.addInterface(Resolver.type(absoluteDB, ext[i]));
requireRemap = true;
}
}
}
else if(cls.getInterfaces() != null){
for(let j in cls.implements){
cls.implements[j] = Resolver.type(absoluteDB, cls.implements[j]);
}
}
// update or create field nodes relations
if(override){
Logger.debug("Overriding fields ",k);
for(let j in v.fields){
o=v.fields[j];
o.fqcn = v.fqcn;
// add relation Field -- parent --> Class
o.enclosingClass = v;
// if the field already exists, check if both differs then update field
if(cls.hasField(o)){
// TODO : Not force override
cls.updateField(o, true);
// update db if signature differs (if type differs)
//absoluteDB.fields.setEntry(o.signature(), o); //hashCode()
}
// if the field not exists, create it
else{
o.fqcn = cls.fqcn;
o.enclosingClass = cls;
cls.addField(o);
// if all its ok, there is not conflict
absoluteDB.fields.setEntry(o.signature(), o);
}
STATS.idxField++;
}
// TODO : if a field is removed from the new version, tag it has "dynamically removed"
}else{
for(let j in cls.fields){
o=cls.fields[j];
// broadcast FQCN from Class objects to Field objects
o.fqcn = cls.fqcn;
o.enclosingClass = cls;
// data.fields[o.hashCode()] = o;
absoluteDB.fields.setEntry(o.signature(), o); //hashCode()
STATS.idxField++;
}
}
// update or create methods nodes relations
if(override){
Logger.debug("Overriding methods ",k);
for(let j in v.methods){
o=v.methods[j];
// add relation Method -- parent --> Class
o.enclosingClass = v;
// if the method already exists, check if both differs then update method
if(cls.hasMethod(o)){
// TODO : Not force override
cls.updateMethod(o, true);
// update db if signature differs (if type differs)
//absoluteDB.fields.setEntry(o.signature(), o); //hashCode()
}
// if the field not exists, create it
else{
o.enclosingClass = cls;
cls.addMethod(o);
// if all its ok, there is not conflict
absoluteDB.methods.setEntry(o.signature(), o);
}
STATS.idxMethod++;
}
}else{
for(let j in cls.methods){
o=cls.methods[j];
o.enclosingClass = cls;
//data.methods[o.signature()] = o;
//absoluteDB.methods[o.signature()] = o;
absoluteDB.methods.setEntry(o.signature(), o);
STATS.idxMethod++;
}
}
});
// create packages nodes
data.classes.map((k,v)=>{
// Build Package instance from the package name (string)
if(absoluteDB.packages.hasEntry(v.package) == false){
absoluteDB.packages.setEntry(v.package, new ModelPackage(v.package));
}
// Append the current class to its Package instance
absoluteDB.packages.getEntry(v.package).childAppend(v);
// Replace the package name by the reference to the package instance into the class instance
v.package = absoluteDB.packages.getEntry(v.package);
// discover inherited and override methods (build Class Hierarchy)
if(v.getSuperClass() != null){
let n=v, sc=null, supers=[];
while((sc = n.getSuperClass()) !=null){
if(sc instanceof CLASS.Class){
scr = absoluteDB.classes.getEntry(sc.name);
}else{
scr = absoluteDB.classes.getEntry(sc);
}
if(scr == null){
if(sc instanceof CLASS.Class){
Logger.debug("Class ("+sc.name+") not found");
}
else{
Logger.debug("Reference ("+sc+") not found");
}
break;
}
supers.push(scr);
n = scr;
if(scr.getSuperClass ==undefined)
break;
}
v.setSupersList(supers);
}
});
Logger.info("DB size : "+absoluteDB.classes.size());
let off=0; mr=0;
let t=0, t1=0;
// console : progress "bar"
data.classes.map((k,v)=>{
let em, om, ovr;
if(v instanceof CLASS.Class){
// analyze each instructions
for(let j in v.methods){
if(v.methods[j] instanceof CLASS.Method){
//mapInstructionFrom(data.classes[i].methods[j], data, STATS);
t = (new Date()).getTime();
mapInstructionFrom(v.methods[j], absoluteDB, STATS);
t1 = (new Date()).getTime();
if(t1-t>150)
Logger.debug((t1-t)+" : "+v.methods[j].signature());
}
}
off++;
if(off%200==0 || off==step)
Logger.info(off+"/"+step+" Classes mapped ("+k+")") ;
}
else{
mr++;
if(mr%20==0) Logger.debug(mr+" missing classes");
}
});
Logger.raw("[*] "+STATS.idxMethod+" methods indexed");
Logger.raw("[*] "+STATS.idxField+" fields indexed");
Logger.raw("[*] "+STATS.instrCtr+" instructions indexed");
//console.log("[*] "+absoluteDB.strings.length+" strings indexed");
Logger.raw("[*] "+STATS.methodCalls+" method calls mapped");
Logger.raw("[*] "+STATS.fieldCalls+" field calls mapped");
// update place where field are called
//return data;
}
/**
* Represents the Application map and the entrypoint for all analysis tasks
*
* @param {string} encoding The file encoding to use when the bytecode is read (default: raw)
* @param {Finder} finder The instance of search engine
* @constructor
*/
function Analyzer(encoding, finder, ctx=null){
SmaliParser.setContext(ctx);
var db = this.db = new AnalyzerDatabase(ctx);
let tempDb = this.tempDb = new AnalyzerDatabase(ctx, 'inmemory');
this.context = ctx;
this.finder = finder;
this.projectionEngines = {};
var config = {
wsPath: null,
encoding: encoding
};
this.newTempDb = function(){
return new AnalyzerDatabase(ctx);
}
this.file = function(filePath, filename, force=false){
//console.log(filePath, filename.endsWith(".smali"));
if(!filename.endsWith(".smali") && !force)
return;
// TODO : replace readFile + string.split by stream
// TODO : test UTF8 support
let src=null, rs=0, cls=null, o=null;
let stremParser = false;
// parse file using blocking IO and string split
if(stremParser){
o = SmaliParser.parseStream(filePath, config.encoding, function( pClass){
tempDb.classes.addEntry(pClass.fqcn, pClass);
rs++;
});
}else{
src=fs.readFileSync(filePath,config.encoding);
cls= SmaliParser.parse(src);
tempDb.classes.addEntry(cls.fqcn, cls);
}
// parse file using stream
//while(rs<1);
//tempDb.classes[cls.fqcn] = cls;
//tempDb.classesCtr += 1;
/*
db.classes[cls.fqcn] = cls;
db.classesCtr+=1; */
};
this.debug = {
notbinded: ()=>{ return new FinderResult(db.notbinded.getAll()) },
unmapped: ()=>{ return new FinderResult(db.unmapped.getAll()) }
};
this.path = function(path){
ctx.bus.send(new Event({
name: "analyze.file.before",
data: {
path: path,
analyzer: this
}
}));
tempDb = this.newTempDb();
// TODO : hcek if path exists;
// ut.forEachFileOf(path,this.file,".smali");
//ut.forEachFileOf(path,this.file);
ut.forEachFileOf(path,(path,file)=>{
this.file(path,file,false);
});
STATS.idxClass = this.db.classes.size();
Logger.raw("[*] Smali analyzing done.\n---------------------------------------")
Logger.raw("[*] "+tempDb.classes.size()+" classes analyzed. ");
// start object mapping
// MakeMap(this.db);
MakeMap(tempDb, this.db);
ctx.bus.send(new Event({
name: "analyze.file.after",
data: {
path: path,
analyzer: this
}
}));
this.finder.updateDB(this.db);
};
/**
* To get the internal database
*/
this.getData = function(){
Logger.debug("[ERROR::DEV] Deprecated function Analyzer::getData() is called ");
return this._db;
}
}
Analyzer.prototype.getContext = function(){
return this.context;
}
/**
* To get the absolute DB
* @returns {AnalyzerDatabase} DB instance
*/
Analyzer.prototype.getInternalDB = function(){
return this.db;
}
Analyzer.prototype.addClassFromFqcn = function(fqcn){
let pkg = null;
let pkgn = fqcn.substr(0,fqcn.lastIndexOf('.'));
if(this.db.packages.hasEntry(pkgn)==true){
pkg = this.db.packages.getEntry(pkgn);
}else{
pkg = new ModelPackage(pkgn);
Logger.debug(pkg);
this.db.packages.setEntry(pkgn, pkg);
}
//console.log(pkgn,pkg, this.db.packages.hasEntry(pkgn));
var cls = new CLASS.Class({
fqcn: fqcn,
name: fqcn, // deprecated
simpleName: fqcn.substr(fqcn.lastIndexOf('.')+1),
package: pkg
});
Logger.debug(cls);
pkg.childAppend(cls);
this.db.classes.setEntry(fqcn, cls);
return cls;
}
Analyzer.prototype.addTagCategory = function(name, taglist){
this.db.tagcategories.addEntry(name, new CLASS.TagCategory(name,taglist));
}
Analyzer.prototype.getTagCategories = function(){
return this.db.tagcategories.getAll();
}
/**
* To initialize the list of syscalls to use
* @param {*} syscalls
* @function
*/
Analyzer.prototype.useSyscalls = function(syscalls){
//this.db.syscalls = {};
for(let i=0; i<syscalls.length ; i++){
for(let j=0; j<syscalls[i].sysnum.length; j++){
if(syscalls[i].sysnum[j]>-1){
this.db.syscalls.addEntry(syscalls[i].sysnum[j], syscalls[i]);
}
}
}
};
/**
* To analyze the decompiled class of Android.jar
* @param {String} path Path of the folder containing .smali files
*/
Analyzer.prototype.system = function(path){
// TODO : hcek if path exists;
//ut.forEachFileOf(path,this.file,".smali");
ut.forEachFileOf(path,(path,file)=>{
this.file(path,file,false);
});
STATS.idxClass = this.db.classes.size();
Logger.raw("[*] Smali analyzing done.\n---------------------------------------")
Logger.raw("[*] "+STATS.idxClass+" classes analyzed. ");
// start object mapping
MakeMap(this.db);
this.finder.updateDB(this.db);
}
/**
* @deprecated
*/
Analyzer.prototype.flattening = function(method){
let instr = [], meta={};
for(let i in method.instr){
meta = {
label: (method.instr[i].tag !== null)? method.instr[i].tag : null,
line: method.instr[i].line
}
for(let j in method.instr[i].stack){
instr.push(method.instr[i].stack[j]);
if(j==0){
instr[instr.length-1].meta = meta;
}
}
}
return instr;
}
/**
* @deprecated
*/
Analyzer.prototype.findBasicBlocks = function(instr){
let bblocks = [], blk={};
blk = {stack:[], next:[], label:null };
for(let i in instr){
if(instr[i].meta !== undefined && (instr[i].meta.label !== null)){
if(blk.stack.length > 0 && i>0){
blk.parent = bblocks[bblocks.length-1];
bblocks.push(blk);
}
blk = {stack:[], next:[], label:instr[i].meta.label };
blk.stack.push(instr[i]);
}
else if(instr[i].opcode.type==CONST.INSTR_TYPE.IF){
blk.stack.push(instr[i]);
blk.parent = bblocks[bblocks.length-1];
bblocks.push(blk);
blk = {stack:[], next:[], label:null };
}
/*else if(instr[i].opcode.type==CONST.INSTR_TYPE.SWITCH){
bblocks.push(blk);
blk = {stack:[], next:[]};
}*/
else if(instr[i].opcode.type==CONST.INSTR_TYPE.GOTO){
//blk.node.pu
bblocks.push(blk);
blk = {stack:[], next:[], label:null };
}
/*
else if(instr[i].opcode.flag & CONST.OPCODE_TYPE.SETS_REGISTER){
bblocks.push(blk);
blk = {stack:[]};
}*/
else{
blk.stack.push(instr[i]);
}
}
return bblocks;
}
/**
* To find a basic block by its label into a basic block list
* @function
* @deprecated
*/
Analyzer.prototype.findBBbyLabel = function(bblocks,label){
for(let i=0; i<bblocks.length; i++){
bblocks[i].offset = i;
if(bblocks[i].label !== null && bblocks[i].label==label){
return bblocks[i];
}
}
return null;
};
/**
* Naive bb tree build by following only conditions and gotos (no try/catch, no switch, ...)
* @function
* @deprecated
*/
Analyzer.prototype.makeTree = function(bblocks){
let last = {};
for(let i=0; i<bblocks.length; i++){
bblocks[i].offset = i;
if(bblocks[i].stack.length > 0){
last = bblocks[i].stack[bblocks[i].stack.length-1];
switch(last.opcode.type){
case CONST.INSTR_TYPE.IF:
bblocks[i].next.push({
jump: CONST.BRANCH.IF_TRUE,
block: this.findBBbyLabel(bblocks,last.right.name)
});
bblocks[i].next.push({
jump: CONST.BRANCH.IF_FALSE,
block: bblocks[i+1]
});
break;
case CONST.INSTR_TYPE.GOTO:
bblocks[i].next.push({
jump: CONST.BRANCH.INCONDITIONNAL_GOTO,
block: this.findBBbyLabel(bblocks,last.right.name)
});
break;
default:
if(bblocks[i+1] != null && bblocks[i+1].label != null){
bblocks[i].next.push({
jump: CONST.BRANCH.INCONDITIONNAL,
block: bblocks[i+1]
});
}
break;
}
}
}
return bblocks;
}
/**
* Use by graph builder
* @function
* @deprecated
*/
Analyzer.prototype.showBlock = function(blk,prefix,styleFn){
if(blk==null) return;
for(let i in blk.stack){
Logger.info(prefix+styleFn("| "+blk.stack[i]._raw));
//if()
}
//console.log(styleFn("-------------------------------------"));
};
/**
* Use by graph builder
* @function
* @deprecated
*/
Analyzer.prototype.showCFG_old = function(bblocks, prefix=""){
let pathTRUE = Chalk.green(prefix+" |\n"+prefix+" |\n"+prefix+" |\n"+prefix+" +-----[TRUE]-->");
let path_len = " +-----[TRUE]-->".length;
let pathFALSE = Chalk.red(prefix+" |\n"+prefix+" |\n"+prefix+" |\n"+prefix+" +-----[FALSE]->");
let pathNEXT = Chalk.yellow(prefix+" |\n"+prefix+" |\n"+prefix+" |\n"+prefix+" V");
let mockFn = x=>x;
for(let i=0; i<bblocks.length; i++){
this.showBlock(bblocks[i], prefix, mockFn);
if(bblocks[i].next.length > 1){
prefix += " ".repeat(path_len);
for(let j in bblocks[i].next){
switch(bblocks[i].next[j].jump){
case CONST.BRANCH.IF_TRUE:
Logger.info(prefix+Chalk.bold.green("if TRUE :"));
this.showBlock(bblocks[i].next[j].block, prefix, Chalk.green);
break;
case CONST.BRANCH.IF_FALSE:
Logger.info(prefix+Chalk.bold.red("if FALSE :"));
this.showBlock(bblocks[i].next[j].block, prefix, Chalk.red);
break;
}
}
}
else if(bblocks[i].next.length == 1){
Logger.info(pathNEXT);
this.showBlock(bblocks[i].next[j].block, prefix, Chalk.white);
}
}
}
/**
* @deprecated
*/
Analyzer.prototype.showCFG = function(bblocks, offset=0, prefix="", fn=null){
if(bblocks.length==0 || bblocks[offset]==undefined){
Logger.debug(offset+" => not block");
return null;
}
let pathTRUE = Chalk.green(prefix+" |\n"+prefix+" |\n"+prefix+" |\n"+prefix+" +-----[TRUE]-->");
let path_len = 6;" +-----[TRUE]-->".length;
let pathFALSE = Chalk.red(prefix+" |\n"+prefix+" |\n"+prefix+" |\n"+prefix+" +-----[FALSE]->");
let pathNEXT = Chalk.yellow(prefix+" |\n"+prefix+" |\n"+prefix+" |\n"+prefix+" V");
let mockFn = x=>x;
this.showBlock(bblocks[offset], prefix, (fn==null)? mockFn : fn);
if(bblocks[offset].next.length > 1){
prefix += " ".repeat(path_len);
for(let j in bblocks[offset].next){
switch(bblocks[offset].next[j].jump){
case CONST.BRANCH.IF_TRUE:
Logger.info(prefix+Chalk.bold.green("if TRUE :"));
//this.showBlock(bblocks[offset].next[j], prefix, Chalk.green);
if(bblocks[offset].next[j].block == null){
}else{
this.showCFG(bblocks, bblocks[offset].next[j].block.offset+1, prefix, Chalk.green);
}
// this.showCFG(bblocks, bblocks[offset].next[j].offset+1, prefix);
break;
case CONST.BRANCH.IF_FALSE:
Logger.info(prefix+Chalk.bold.red("if FALSE :"));
//this.showBlock(bblocks[offset].next[j], prefix, Chalk.red);
this.showCFG(bblocks, offset+1, prefix, Chalk.red);
break;
}
}
}
else if(bblocks[offset].next.length == 1){
this.showCFG(bblocks, offset+1, prefix, Chalk.yellow);
//console.log(pathNEXT);
//this.showBlock(bblocks[i].next[j].block, prefix, Chalk.white);
}
}
/**
* @deprecated
*/
Analyzer.prototype.cfg = function(method){
let instr = [], meta={}, bblocks = [], blk={};
// list instr
instr = this.flattening(method);
// find basic block
bblocks = this.findBasicBlocks(instr);
// get tree
bblocks = this.makeTree(bblocks);
// show
this.showCFG(bblocks,0);
return bblocks;
}
/**
* TODO
* @param {Class} cls New class to insert into the model
*/
Analyzer.prototype.updateWithClass = function(cls){
};
/**
* @function
* @deprected
*/
Analyzer.prototype._updateWithEachFileOf = function(filesDB, update_strategy){
//this.db.files
this.db.files.map((k,v)=>{
for(let j=0; j<filesDB.length; j++){
update_strategy( this.db, filesDB[j], v);
}
});
};
/**
* @function
* @deprecated
*/
Analyzer.prototype.updateFiles = function(filesDB, override){
this._updateWithEachFileOf(
filesDB,
// check if the file can be treated
function(db, inFile, dbFile){
if((inFile.path == dbFile.path)||override){
//dbFile.update(inFile);
}else{
db.files.insert(inFile);
}
}
)
};
Analyzer.prototype.insertIn = function(category, inData){
if(inData instanceof Array){
for(let i=0; i<inData.length; i++){
this.db[category].insert(inData[i]);
}
}else{
for(let i in inData){
this.db[category].addEntry(i, inData[i]);
}
}
}
function tagAsAndroidInternal( pOffset, pElement){
pElement.addTag(AnalysisHelper.TAG.Discover.Internal);
}
Analyzer.prototype.tagAllAsInternal = function(){
this.db.classes.map(tagAsAndroidInternal);
this.db.fields.map(tagAsAndroidInternal);
this.db.methods.map(tagAsAndroidInternal);
this.db.strings.map(tagAsAndroidInternal);
}
Analyzer.prototype.resolveMethod = function(ref){
let m = Resolver.method(this.db, ref);
Logger.debug(m);
return m;
}
Analyzer.prototype.tagAllIf = function(condition, tag){
this.tagIf(condition, "classes", tag);
this.tagIf(condition, "fields", tag);
this.tagIf(condition, "methods", tag);
this.tagIf(condition, "strings", tag);
}
Analyzer.prototype.tagIf = function(condition, type, tag){
this.db[type].map(function(k,v){
if(condition(k,v)){
v.addTag(tag);
}
});
/*
if(this.db[type] instanceof Array){
this.db[type].map(function(x){
if(condition(x)){
x.addTag(tag);
}
});
}else{
for(let k in this.db[type]){
if(condition(this.db[type][k])){
this.db[type][k].addTag(tag);
}
}
}*/
}
/**
* To scan for new DataBlock and index them
*/
Analyzer.prototype.updateDataBlock = function(){
let dd=null, dbs=null;
this.db.methods.map((k,v)=>{
dd = v.getDataBlocks();
for(let j=0; j<dd.length; j++){
if(dd[j] == null) continue;
dbs = dd[j].getUID();
if(this.db.datablock.hasEntry(dbs) === false)
this.db.datablock.addEntry(dbs,dd[j]);
}
});
/*
for(let i in this.db.methods){
dd = this.db.methods[i].getDataBlocks();
for(let j=0; j<dd.length; j++){
if(dd[j] == null) continue;
dbs = dd[j].getUID();
if(this.db.datablock[dbs] == null)
this.db.datablock[dbs] = dd[j];
}
}*/
}
module.exports = Analyzer;