collect stack data

This commit is contained in:
Jean-Marie 'Histausse' Mineau 2025-02-06 16:18:32 +01:00
parent 7713f3247a
commit c11101b46a
Signed by: histausse
GPG key ID: B66AEEDA9B645AD2
5 changed files with 175 additions and 37 deletions

2
frida/.gitignore vendored
View file

@ -1 +1,3 @@
__pycache__ __pycache__
theseus_frida/StackConsumer.dex.b64
consumer/build

View file

@ -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<StackFrame> {
public ArrayList<StackFrame> 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[] {});
}
}

22
frida/consumer/build.sh Normal file
View file

@ -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}"

View file

@ -8,6 +8,7 @@ import frida # type: ignore
from androguard.core.apk import get_apkid # type: ignore from androguard.core.apk import get_apkid # type: ignore
FRIDA_SCRIPT = Path(__file__).parent / "hook.js" FRIDA_SCRIPT = Path(__file__).parent / "hook.js"
STACK_CONSUMER_B64 = Path(__file__).parent / "StackConsumer.dex.b64"
# Define handler to event generated by the scripts # Define handler to event generated by the scripts
@ -25,52 +26,68 @@ def on_message(message, data):
print("[on_message] message:", message) print("[on_message] message:", message)
def get_ty(java_name: str) -> str: def print_stack(stack, prefix: str):
"""Return the android name from the java name of a class / type""" for frame in stack:
# TODO: array native = ""
# TODO: scalar if frame["is_native"]:
if java_name == "V": # tmp stub native = " (native)"
return "V" print(f"{prefix}{frame['method']}:{frame['bytecode_index']}{native}")
return f"L{java_name.replace('.', '/')};"
def get_method_id(method_data) -> str: # def get_ty(java_name: str) -> str:
"""Get a method descriptor from the different elements collected from the methods.""" # """Return the android name from the java name of a class / type"""
name = method_data["name"] # # TODO: array
ret = get_ty(method_data["ret"]) # # TODO: scalar
cls = get_ty(method_data["class"]) # if java_name == "V": # tmp stub
args = "".join(map(get_ty, method_data["args"])) # return "V"
return f"{cls}->{name}({args}){ret}" # 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): def handle_invoke_data(data):
method = get_method_id(data["method"]) method = data["method"]
caller_method = "?" # get_method_id(data["caller_method"]) # caller_method = "?" # get_method_id(data["caller_method"])
addr = data["addr"] # addr = data["addr"]
print("Method.Invoke:") print("Method.Invoke:")
print(f" called: {method}") print(f" called: {method}")
print(f" by: {caller_method}") print(f" stack:")
print(f" at: 0x{addr:08x}") print_stack(data["stack"], " ")
# print(f" by: {caller_method}")
# print(f" at: 0x{addr:08x}")
def handle_class_new_inst_data(data): def handle_class_new_inst_data(data):
constructor = get_method_id(data["constructor"]) constructor = data["constructor"]
caller_method = "?" # get_method_id(data["caller_method"]) # caller_method = "?" # get_method_id(data["caller_method"])
addr = data["addr"] # addr = data["addr"]
print("Class.NewInstance:") print("Class.NewInstance:")
print(f" called: {constructor}") print(f" called: {constructor}")
print(f" by: {caller_method}") print(f" stack:")
print(f" at: 0x{addr:08x}") print_stack(data["stack"], " ")
# print(f" by: {caller_method}")
# print(f" at: 0x{addr:08x}")
def handle_cnstr_new_inst_data(data): def handle_cnstr_new_inst_data(data):
constructor = get_method_id(data["constructor"]) constructor = data["constructor"]
caller_method = "?" # get_method_id(data["caller_method"]) if not constructor.startswith("Lcom/example/theseus"):
addr = data["addr"] return
# caller_method = "?" # get_method_id(data["caller_method"])
# addr = data["addr"]
print("Constructor.newInstance:") print("Constructor.newInstance:")
print(f" called: {constructor}") print(f" called: {constructor}")
print(f" by: {caller_method}") print(f" stack:")
print(f" at: 0x{addr:08x}") print_stack(data["stack"], " ")
# print(f" by: {caller_method}")
# print(f" at: 0x{addr:08x}")
def main(): def main():
@ -105,6 +122,11 @@ def main():
with FRIDA_SCRIPT.open("r") as file: with FRIDA_SCRIPT.open("r") as file:
script = file.read() script = file.read()
with STACK_CONSUMER_B64.open("r") as file:
script = script.replace(
"<PYTHON REPLACE StackConsumer.dex.b64>",
file.read().replace("\n", "").strip(),
)
pid = device.spawn([app]) pid = device.spawn([app])
session = device.attach(pid) session = device.attach(pid)

View file

@ -1,5 +1,69 @@
Java.perform(() => { 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("<PYTHON REPLACE StackConsumer.dex.b64>", 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() +
"->" +
"<init>" +
"(" +
cnstr.getParameterTypes().map((argty) => argty.descriptorString()).join('') +
")V";
};
const Method = Java.use("java.lang.reflect.Method"); const Method = Java.use("java.lang.reflect.Method");
const Class = Java.use("java.lang.Class"); const Class = Java.use("java.lang.Class");
const Constructor = Java.use("java.lang.reflect.Constructor"); const Constructor = Java.use("java.lang.reflect.Constructor");
@ -9,14 +73,14 @@ Java.perform(() => {
send({ send({
"type": "invoke", "type": "invoke",
"data": { "data": {
"method": { "method": get_method_dsc(this),
/*{
"name": this.getName(), "name": this.getName(),
"class": this.getDeclaringClass().getName(), "class": this.getDeclaringClass().getName(),
"args": this.getParameterTypes().map((argty) => argty.getName() ), "args": this.getParameterTypes().map((argty) => argty.getName() ),
"ret": this.getReturnType().getName(), "ret": this.getReturnType().getName(),
}, },*/
"caller_method": "?", "stack": get_stack()
"addr": 0,
} }
}); });
return this.invoke(obj, args); return this.invoke(obj, args);
@ -26,14 +90,16 @@ Java.perform(() => {
send({ send({
"type": "class-new-inst", "type": "class-new-inst",
"data": { "data": {
"constructor": { "constructor": this.descriptorString() + "-><init>()V",
/*{
"name": "<init>", "name": "<init>",
"class": this.getName(), "class": this.getName(),
"args": [], "args": [],
"ret": "V", "ret": "V",
}, },*/
"caller_method": "?", "caller_method": "?",
"addr": 0, "addr": 0,
"stack": get_stack()
} }
}); });
return this.newInstance(); return this.newInstance();
@ -44,19 +110,22 @@ Java.perform(() => {
send({ send({
"type": "cnstr-new-isnt", "type": "cnstr-new-isnt",
"data": { "data": {
"constructor": { "constructor": get_constr_dsc(this),
/*
{
"name": "<init>", "name": "<init>",
"class": this.getDeclaringClass().getName(), "class": this.getDeclaringClass().getName(),
"args": this.getParameterTypes().map((argty) => argty.getName()), "args": this.getParameterTypes().map((argty) => argty.getName()),
"ret": "V", "ret": "V",
}, },
*/
"caller_method": "?", "caller_method": "?",
"addr": 0, "addr": 0,
"stack": get_stack()
} }
}); });
return this.newInstance(args); return this.newInstance(args);
}; };
}); });