358 lines
12 KiB
JavaScript
358 lines
12 KiB
JavaScript
function dump_classloaders() {
|
|
Java.perform(() => {
|
|
const System = Java.use('java.lang.System');
|
|
var class_loader = Java.enumerateClassLoadersSync();
|
|
for (var cl of class_loader) {
|
|
send({"type": "classloader", "data": {
|
|
"id": System.identityHashCode(cl),
|
|
"parent_id": System.identityHashCode(cl.getParent()),
|
|
"str": cl.toString(),
|
|
"cname": cl.$className
|
|
}});
|
|
}
|
|
send({"type": "classloader-done"})
|
|
});
|
|
}
|
|
|
|
function registerStackConsumer() {
|
|
const Consumer = Java.use('java.util.function.Consumer');
|
|
const Method = Java.use('java.lang.reflect.Method');
|
|
const ArrayList = Java.use('java.util.ArrayList');
|
|
const StackFrame = Java.use('java.lang.StackWalker$StackFrame');
|
|
|
|
// Finding r8 optimized method for the Consumer interface
|
|
let requiredMethods = Consumer.class.getDeclaredMethods();
|
|
var lambdamethod = '';
|
|
requiredMethods.forEach(m => {
|
|
var meth = Java.cast(m, Method);
|
|
let methodname = meth.getName();
|
|
if (methodname.startsWith("$r8$lambda$")) {
|
|
lambdamethod = methodname;
|
|
};
|
|
});
|
|
|
|
return Java.registerClass({
|
|
name: "theseus.android.StackConsumer",
|
|
implements: [Consumer],
|
|
fields: {
|
|
stack: 'java.util.ArrayList',
|
|
},
|
|
methods: {
|
|
'<init>': [{
|
|
returnType: 'void',
|
|
argumentTypes: [],
|
|
implementation: function () {
|
|
this.stack.value = ArrayList.$new();
|
|
}
|
|
}],
|
|
accept(frame) {
|
|
var castedFrame = Java.cast(frame, StackFrame);
|
|
this.stack.value.add(castedFrame);
|
|
},
|
|
getStack: [{
|
|
returnType: '[Ljava.lang.StackWalker$StackFrame;',
|
|
argumentTypes: [],
|
|
implementation: function () {
|
|
return this.stack.value.toArray(Java.array('java.lang.StackWalker$StackFrame', []));
|
|
},
|
|
}],
|
|
andThen(cons) {
|
|
return this.$super.andThen(cons);
|
|
},
|
|
lambda$andThen$0(consumer, obj) {},
|
|
['_' + lambdamethod]: function (cons1, cons2, obj) {}
|
|
},
|
|
});
|
|
}
|
|
|
|
// recv('dump-class-loaders', function onMessage(msg) {dump_classloaders()});
|
|
|
|
Java.perform(() => {
|
|
|
|
/*
|
|
//const StackFrameInfo = Java.use('java.lang.StackFrameInfo');
|
|
const Consumer = Java.use('java.util.function.Consumer');
|
|
const System = Java.use('java.lang.System');
|
|
*/
|
|
|
|
const StackWalker = Java.use('java.lang.StackWalker');
|
|
const StackWalkerOptions = Java.use('java.lang.StackWalker$Option');
|
|
const StackWalkerOptionsShowHidden = StackWalkerOptions.valueOf("SHOW_HIDDEN_FRAMES");
|
|
const StackWalkerOptionsShowReflect = StackWalkerOptions.valueOf("SHOW_REFLECT_FRAMES");
|
|
const StackWalkerOptionsRetainClassReference = StackWalkerOptions.valueOf("RETAIN_CLASS_REFERENCE");
|
|
const StackFrame = Java.use('java.lang.StackWalker$StackFrame');
|
|
const Base64 = Java.use("android.util.Base64");
|
|
const InMemoryDexClassLoader = Java.use("dalvik.system.InMemoryDexClassLoader");
|
|
const ByteBuffer = Java.use("java.nio.ByteBuffer");
|
|
const Method = Java.use("java.lang.reflect.Method");
|
|
const Class = Java.use("java.lang.Class");
|
|
const Constructor = Java.use("java.lang.reflect.Constructor");
|
|
const Modifier = Java.use("java.lang.reflect.Modifier");
|
|
const DexFile = Java.use("dalvik.system.DexFile");
|
|
const File = Java.use('java.io.File');
|
|
const Files = Java.use('java.nio.file.Files');
|
|
const Path = Java.use('java.nio.file.Path');
|
|
const System = Java.use('java.lang.System');
|
|
const Arrays = Java.use('java.util.Arrays');
|
|
|
|
/*
|
|
const myClassLoader = InMemoryDexClassLoader.$new(
|
|
ByteBuffer.wrap(Base64.decode("<PYTHON REPLACE StackConsumer.dex.b64>", Base64.DEFAULT.value)),
|
|
null
|
|
);
|
|
const StackConsumer = Java.ClassFactory.get(myClassLoader).use("theseus.android.StackConsumer");
|
|
*/
|
|
const StackConsumer = registerStackConsumer();
|
|
|
|
const get_stack = function () {
|
|
// console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));
|
|
//
|
|
// TODO: use this instead? (https://developer.android.com/reference/java/lang/StackTraceElement)
|
|
// Pro: - more robust (cf crash of maltoy app)
|
|
// - Works with any android version
|
|
// Con: - Use java string: may require a lot of parsing
|
|
// - Use getLineNumber is iffy: returns either a line number when debug info are available or the dex address
|
|
// when no debug info. We prefere the address, but does this means we need to strip the apk before running?
|
|
// var stack = Java.use("java.lang.Exception").$new().getStackTrace();
|
|
// for (var i = 0; i < stack.length; i++) {
|
|
// console.log(stack[i].toString());
|
|
// }
|
|
// return [];
|
|
var stackConsumer = StackConsumer.$new();
|
|
var walker = StackWalker.getInstance(StackWalkerOptionsRetainClassReference);
|
|
walker.forEach(stackConsumer);
|
|
//walker.walk(stackConsumer.walkNFrame(20));
|
|
var stack = stackConsumer.getStack()
|
|
//send({"type": "stack", "data": stackConsumer.getStack()});
|
|
return stack.map((frame) => {
|
|
return {
|
|
"bytecode_index": frame.getByteCodeIndex(),
|
|
"is_native": frame.isNativeMethod(),
|
|
"method": frame.getDeclaringClass().descriptorString() + "->" + frame.getMethodName() + frame.getDescriptor(),
|
|
"cl_id": System.identityHashCode(frame.getDeclaringClass().getClassLoader()),
|
|
//{
|
|
//"descriptor": frame.getDescriptor(),
|
|
//"name": frame.getMethodName(),
|
|
//"class": frame.getDeclaringClass().descriptorString(),
|
|
// Broken for some reason
|
|
//"args": frame.getMethodType().parameterArray().map((argty) => argty.getName()),
|
|
//"ret": frame.getMethodType().returnType().getName(),
|
|
//}
|
|
};
|
|
});
|
|
};
|
|
const get_method_dsc = function (mth) {
|
|
// TODO: find a way to use MethodType (https://developer.android.com/reference/java/lang/invoke/MethodType)
|
|
// MethodType.descriptorString()
|
|
return mth.getDeclaringClass().descriptorString() +
|
|
"->" +
|
|
mth.getName() +
|
|
"(" +
|
|
mth.getParameterTypes().map((argty) => argty.descriptorString()).join('') +
|
|
")" +
|
|
mth.getReturnType().descriptorString();
|
|
};
|
|
const get_constr_dsc = function (cnstr) {
|
|
// TODO: find a way to use MethodType (https://developer.android.com/reference/java/lang/invoke/MethodType)
|
|
// MethodType.descriptorString()
|
|
return cnstr.getDeclaringClass().descriptorString() +
|
|
"->" +
|
|
"<init>" +
|
|
"(" +
|
|
cnstr.getParameterTypes().map((argty) => argty.descriptorString()).join('') +
|
|
")V";
|
|
};
|
|
|
|
|
|
// ****** Reflexive Method Calls ******
|
|
|
|
// Method.invoke(obj, ..args)
|
|
Method.invoke.overload(
|
|
"java.lang.Object", "[Ljava.lang.Object;" // the Frida type parser is so cursted...
|
|
).implementation = function (obj, args) {
|
|
send({
|
|
"type": "invoke",
|
|
"data": {
|
|
"method": get_method_dsc(this),
|
|
"method_cl_id": System.identityHashCode(this.getDeclaringClass().getClassLoader()),
|
|
/*{
|
|
"name": this.getName(),
|
|
"class": this.getDeclaringClass().getName(),
|
|
"args": this.getParameterTypes().map((argty) => argty.getName() ),
|
|
"ret": this.getReturnType().getName(),
|
|
},*/
|
|
"stack": get_stack(),
|
|
"is_static": Modifier.isStatic(this.getModifiers()),
|
|
}
|
|
});
|
|
return this.invoke(obj, args);
|
|
};
|
|
|
|
// ****** Reflexive Class Instantiation ******
|
|
|
|
// Class.newInstance()
|
|
Class.newInstance.overload(
|
|
).implementation = function () {
|
|
send({
|
|
"type": "class-new-inst",
|
|
"data": {
|
|
"constructor": this.descriptorString() + "-><init>()V",
|
|
"constructor_cl_id": System.identityHashCode(this.getClassLoader()),
|
|
/*{
|
|
"name": "<init>",
|
|
"class": this.getName(),
|
|
"args": [],
|
|
"ret": "V",
|
|
},*/
|
|
"caller_method": "?",
|
|
"addr": 0,
|
|
"stack": get_stack()
|
|
}
|
|
});
|
|
return this.newInstance();
|
|
};
|
|
// Constructor.newInstance(..args)
|
|
Constructor.newInstance.overload(
|
|
"[Ljava.lang.Object;"
|
|
).implementation = function (args) {
|
|
send({
|
|
"type": "cnstr-new-isnt",
|
|
"data": {
|
|
"constructor": get_constr_dsc(this),
|
|
"constructor_cl_id": System.identityHashCode(this.getDeclaringClass().getClassLoader()),
|
|
/*
|
|
{
|
|
"name": "<init>",
|
|
"class": this.getDeclaringClass().getName(),
|
|
"args": this.getParameterTypes().map((argty) => argty.getName()),
|
|
"ret": "V",
|
|
},
|
|
*/
|
|
"caller_method": "?",
|
|
"addr": 0,
|
|
"stack": get_stack()
|
|
}
|
|
});
|
|
return this.newInstance(args);
|
|
};
|
|
|
|
// ****** Dynamic Class Loading ******
|
|
|
|
// DexFile.openDexFileNative(sourceName, outputName, flags, loader, elements): load .dex from file
|
|
// See https://cs.android.com/android/platform/superproject/main/+/main:libcore/dalvik/src/main/java/dalvik/system/DexFile.java;drc=2f8a31e93fc238a88a48bfeed82557e07e1d5003;l=477
|
|
// https://cs.android.com/android/platform/superproject/main/+/main:art/runtime/native/dalvik_system_DexFile.cc;drc=3d19fbcc09b1b44928639b06cd0b88f735cd988d;l=368
|
|
DexFile.openDexFileNative.overload(
|
|
'java.lang.String',
|
|
'java.lang.String',
|
|
'int',
|
|
'java.lang.ClassLoader',
|
|
'[Ldalvik.system.DexPathList$Element;',
|
|
).implementation = function (
|
|
sourceName,
|
|
outputName,
|
|
flags,
|
|
loader,
|
|
elements,
|
|
) {
|
|
let file = File.$new(sourceName);
|
|
|
|
let path = Path.of(sourceName, []);
|
|
let dex = Files.readAllBytes(path);
|
|
let b64 = Base64.encodeToString(dex, Base64.DEFAULT.value);
|
|
let classloader_class = null;
|
|
let classloader_id = System.identityHashCode(loader);
|
|
if (loader !== null) {
|
|
classloader_class = loader.getClass().descriptorString();
|
|
}
|
|
send({
|
|
"type": "load-dex",
|
|
"data": {
|
|
"dex": [b64],
|
|
"classloader_class": classloader_class,
|
|
"classloader": classloader_id,
|
|
"classloader_parent": System.identityHashCode(loader.getParent()),
|
|
}
|
|
});
|
|
|
|
let is_wr = file.canWrite();
|
|
if (is_wr) {
|
|
file.setReadOnly();
|
|
}
|
|
let result = this.openDexFileNative(
|
|
sourceName,
|
|
outputName,
|
|
flags,
|
|
loader,
|
|
elements,
|
|
);
|
|
/* TODO: FIX
|
|
if (is_wr) {
|
|
file.setWritable(true, false);
|
|
}
|
|
*/
|
|
return result;
|
|
};
|
|
// DexFile.openInMemoryDexFilesNative(bufs, arrays, starts, ends, loader,elements): load .dex from memory
|
|
// See https://cs.android.com/android/platform/superproject/main/+/main:libcore/dalvik/src/main/java/dalvik/system/DexFile.java;drc=2f8a31e93fc238a88a48bfeed82557e07e1d5003;l=431
|
|
// https://cs.android.com/android/platform/superproject/main/+/main:art/runtime/native/dalvik_system_DexFile.cc;l=253;drc=3d19fbcc09b1b44928639b06cd0b88f735cd988d
|
|
DexFile.openInMemoryDexFilesNative.overload(
|
|
'[Ljava.nio.ByteBuffer;',
|
|
'[[B',
|
|
'[I',
|
|
'[I',
|
|
'java.lang.ClassLoader',
|
|
'[Ldalvik.system.DexPathList$Element;',
|
|
).implementation = function (
|
|
bufs,
|
|
arrays,
|
|
starts,
|
|
ends,
|
|
loader,
|
|
elements,
|
|
) {
|
|
let dex = [];
|
|
// openInMemoryDexFilesNative() checks bufs.length == arrays.length == starts.length === ends.length
|
|
for (let i = 0; i < bufs.length; i++) {
|
|
let s = starts[i];
|
|
let e = starts[i];
|
|
// openInMemoryDexFilesNative() checks s < e
|
|
let array = arrays[i];
|
|
let buf = bufs[i];
|
|
let raw = [];
|
|
// match code from art/runtime/native/dalvik_system_DexFile.cc commit 3d19fbcc09b1b44928639b06cd0b88f735cd988d
|
|
if (array === null) {
|
|
raw = Arrays.copyOf([], e-s);
|
|
raw = buf.get(s, raw, 0, e-s);
|
|
} else {
|
|
raw = Arrays.copyOfRange(array, s, e);
|
|
}
|
|
let b64 = Base64.encodeToString(raw, Base64.DEFAULT.value);
|
|
dex.push(b64);
|
|
}
|
|
|
|
let classloader_class = "";
|
|
let classloader_id = System.identityHashCode(loader);
|
|
if (loader !== null) {
|
|
classloader_class = loader.getClass().descriptorString();
|
|
}
|
|
send({
|
|
"type": "load-dex",
|
|
"data": {
|
|
"dex": dex,
|
|
"classloader_class": classloader_class,
|
|
"classloader": classloader_id,
|
|
"classloader_parent": System.identityHashCode(loader.getParent()),
|
|
}
|
|
});
|
|
return this.openInMemoryDexFilesNative(
|
|
bufs,
|
|
arrays,
|
|
starts,
|
|
ends,
|
|
loader,
|
|
elements,
|
|
);
|
|
};
|
|
});
|
|
|
|
recv('dump-class-loaders', function onMessage(msg) {dump_classloaders()});
|