diff --git a/frida/.gitignore b/frida/.gitignore index bee8a64..2c0ce8e 100644 --- a/frida/.gitignore +++ b/frida/.gitignore @@ -1 +1,3 @@ __pycache__ +theseus_frida/StackConsumer.dex.b64 +consumer/build diff --git a/frida/consumer/StackConsumer.java b/frida/consumer/StackConsumer.java new file mode 100644 index 0000000..8fbae47 --- /dev/null +++ b/frida/consumer/StackConsumer.java @@ -0,0 +1,23 @@ +package theseus.android; + +import java.util.function.Consumer; +import java.lang.StackWalker.StackFrame; +import java.util.ArrayList; + +class StackConsumer implements Consumer { + + public ArrayList stack; + + public StackConsumer() { + this.stack = new ArrayList(); + } + + @Override + public void accept(StackFrame frame) { + stack.add(frame); + } + + public StackFrame[] getStack() { + return this.stack.toArray(new StackFrame[] {}); + } +} diff --git a/frida/consumer/build.sh b/frida/consumer/build.sh new file mode 100644 index 0000000..201516d --- /dev/null +++ b/frida/consumer/build.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + + +JAVAC='/usr/lib/jvm/java-17-openjdk/bin/javac' +SDK_TOOLS="${HOME}/Android/Sdk/" +VERSION='34.0.0' +D8="${SDK_TOOLS}/build-tools/${VERSION}/d8" +VERSION_B=$(echo "${VERSION}" | sed 's/\..*//') +ANDROID_JAR="${SDK_TOOLS}/platforms/android-${VERSION_B}/android.jar" + +FOLDER=$(dirname "$(realpath $0)") +BUILD_F="${FOLDER}/build" +OUT_FILE="${FOLDER}/../theseus_frida/StackConsumer.dex.b64" +rm -r "${BUILD_F}" +mkdir "${BUILD_F}" + +"${JAVAC}" -d "${BUILD_F}" -classpath "${ANDROID_JAR}" "${FOLDER}/StackConsumer.java" + +mkdir "${BUILD_F}/classes" +"${D8}" --classpath "${ANDROID_JAR}" "${BUILD_F}/theseus/android/StackConsumer.class" --output "${BUILD_F}/classes" + +base64 "${BUILD_F}/classes/classes.dex" > "${OUT_FILE}" diff --git a/frida/theseus_frida/__init__.py b/frida/theseus_frida/__init__.py index a549cb6..483e6ae 100644 --- a/frida/theseus_frida/__init__.py +++ b/frida/theseus_frida/__init__.py @@ -8,6 +8,7 @@ import frida # type: ignore from androguard.core.apk import get_apkid # type: ignore FRIDA_SCRIPT = Path(__file__).parent / "hook.js" +STACK_CONSUMER_B64 = Path(__file__).parent / "StackConsumer.dex.b64" # Define handler to event generated by the scripts @@ -25,52 +26,68 @@ def on_message(message, data): print("[on_message] message:", message) -def get_ty(java_name: str) -> str: - """Return the android name from the java name of a class / type""" - # TODO: array - # TODO: scalar - if java_name == "V": # tmp stub - return "V" - return f"L{java_name.replace('.', '/')};" +def print_stack(stack, prefix: str): + for frame in stack: + native = "" + if frame["is_native"]: + native = " (native)" + print(f"{prefix}{frame['method']}:{frame['bytecode_index']}{native}") -def get_method_id(method_data) -> str: - """Get a method descriptor from the different elements collected from the methods.""" - name = method_data["name"] - ret = get_ty(method_data["ret"]) - cls = get_ty(method_data["class"]) - args = "".join(map(get_ty, method_data["args"])) - return f"{cls}->{name}({args}){ret}" +# def get_ty(java_name: str) -> str: +# """Return the android name from the java name of a class / type""" +# # TODO: array +# # TODO: scalar +# if java_name == "V": # tmp stub +# return "V" +# return f"L{java_name.replace('.', '/')};" + + +# def get_method_id(method_data) -> str: +# """Get a method descriptor from the different elements collected from the methods.""" +# name = method_data["name"] +# ret = get_ty(method_data["ret"]) +# cls = get_ty(method_data["class"]) +# args = "".join(map(get_ty, method_data["args"])) +# return f"{cls}->{name}({args}){ret}" def handle_invoke_data(data): - method = get_method_id(data["method"]) - caller_method = "?" # get_method_id(data["caller_method"]) - addr = data["addr"] + method = data["method"] + # caller_method = "?" # get_method_id(data["caller_method"]) + # addr = data["addr"] print("Method.Invoke:") print(f" called: {method}") - print(f" by: {caller_method}") - print(f" at: 0x{addr:08x}") + print(f" stack:") + print_stack(data["stack"], " ") + # print(f" by: {caller_method}") + # print(f" at: 0x{addr:08x}") def handle_class_new_inst_data(data): - constructor = get_method_id(data["constructor"]) - caller_method = "?" # get_method_id(data["caller_method"]) - addr = data["addr"] + constructor = data["constructor"] + # caller_method = "?" # get_method_id(data["caller_method"]) + # addr = data["addr"] print("Class.NewInstance:") print(f" called: {constructor}") - print(f" by: {caller_method}") - print(f" at: 0x{addr:08x}") + print(f" stack:") + print_stack(data["stack"], " ") + # print(f" by: {caller_method}") + # print(f" at: 0x{addr:08x}") def handle_cnstr_new_inst_data(data): - constructor = get_method_id(data["constructor"]) - caller_method = "?" # get_method_id(data["caller_method"]) - addr = data["addr"] + constructor = data["constructor"] + if not constructor.startswith("Lcom/example/theseus"): + return + # caller_method = "?" # get_method_id(data["caller_method"]) + # addr = data["addr"] print("Constructor.newInstance:") print(f" called: {constructor}") - print(f" by: {caller_method}") - print(f" at: 0x{addr:08x}") + print(f" stack:") + print_stack(data["stack"], " ") + # print(f" by: {caller_method}") + # print(f" at: 0x{addr:08x}") def main(): @@ -105,6 +122,11 @@ def main(): with FRIDA_SCRIPT.open("r") as file: script = file.read() + with STACK_CONSUMER_B64.open("r") as file: + script = script.replace( + "", + file.read().replace("\n", "").strip(), + ) pid = device.spawn([app]) session = device.attach(pid) diff --git a/frida/theseus_frida/hook.js b/frida/theseus_frida/hook.js index c3eec02..f50dea1 100644 --- a/frida/theseus_frida/hook.js +++ b/frida/theseus_frida/hook.js @@ -1,5 +1,69 @@ 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 myClassLoader = InMemoryDexClassLoader.$new( + ByteBuffer.wrap(Base64.decode("", Base64.DEFAULT.value)), + null + ); + const StackConsumer = Java.ClassFactory.get(myClassLoader).use("theseus.android.StackConsumer"); + + const get_stack = function () { + var stackConsumer = StackConsumer.$new(); + var walker = StackWalker.getInstance(StackWalkerOptionsRetainClassReference); + walker.forEach(stackConsumer); + //send({"type": "stack", "data": stackConsumer.getStack()}); + return stackConsumer.getStack().map((frame) => { + return { + "bytecode_index": frame.getByteCodeIndex(), + "is_native": frame.isNativeMethod(), + "method": frame.getDeclaringClass().descriptorString() + "->" + frame.getMethodName() + frame.getDescriptor(), + //{ + //"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() + + "->" + + "" + + "(" + + cnstr.getParameterTypes().map((argty) => argty.descriptorString()).join('') + + ")V"; + }; + const Method = Java.use("java.lang.reflect.Method"); const Class = Java.use("java.lang.Class"); const Constructor = Java.use("java.lang.reflect.Constructor"); @@ -9,14 +73,14 @@ Java.perform(() => { send({ "type": "invoke", "data": { - "method": { + "method": get_method_dsc(this), + /*{ "name": this.getName(), "class": this.getDeclaringClass().getName(), "args": this.getParameterTypes().map((argty) => argty.getName() ), "ret": this.getReturnType().getName(), - }, - "caller_method": "?", - "addr": 0, + },*/ + "stack": get_stack() } }); return this.invoke(obj, args); @@ -26,14 +90,16 @@ Java.perform(() => { send({ "type": "class-new-inst", "data": { - "constructor": { + "constructor": this.descriptorString() + "->()V", + /*{ "name": "", "class": this.getName(), "args": [], "ret": "V", - }, + },*/ "caller_method": "?", "addr": 0, + "stack": get_stack() } }); return this.newInstance(); @@ -44,19 +110,22 @@ Java.perform(() => { send({ "type": "cnstr-new-isnt", "data": { - "constructor": { + "constructor": get_constr_dsc(this), + /* + { "name": "", "class": this.getDeclaringClass().getName(), "args": this.getParameterTypes().map((argty) => argty.getName()), "ret": "V", }, + */ "caller_method": "?", "addr": 0, + "stack": get_stack() } }); return this.newInstance(args); }; - });