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__
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
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(
"<PYTHON REPLACE StackConsumer.dex.b64>",
file.read().replace("\n", "").strip(),
)
pid = device.spawn([app])
session = device.attach(pid)

View file

@ -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("<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 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() + "-><init>()V",
/*{
"name": "<init>",
"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": "<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);
};
});