directly define StackConsumer in frida

This commit is contained in:
Jean-Marie Mineau 2025-04-02 14:26:14 +02:00
parent a0cb49fd77
commit 28f5ac772c
Signed by: histausse
GPG key ID: B66AEEDA9B645AD2
5 changed files with 54 additions and 84 deletions

View file

@ -2,20 +2,3 @@
Collect runtime information about reflection operation done by en application to feed them to the patcher. Collect runtime information about reflection operation done by en application to feed them to the patcher.
The Frida hook uses a Java class to collect the stack information, before building/installing the python package, you need to build the class:
```shell
# If the default values do not match, set the variables:
#
# JAVAC: path to the java compiler
# ANDROID_SDK: path to the android sdk
# VERSION: Android SDK version to use
# D8: path to the d8 executable
# ANDROID_JAR: path to the android.jar file to link
# BUILD_F: build folder (will be delated if exist)
# OUT_FILE: the file where to put the b64 of the compiled dex
bash consumer/build.sh
poetry build # / poetry install / pip install .
```

View file

@ -1,29 +0,0 @@
package theseus.android;
import java.util.function.Consumer;
import java.lang.StackWalker.StackFrame;
import java.util.ArrayList;
import java.util.function.Function;
import java.util.stream.Stream;
class StackConsumer implements Consumer<StackFrame> {
public ArrayList<StackFrame> stack;
public StackConsumer() {
this.stack = new ArrayList<StackFrame>();
}
@Override
public void accept(StackFrame frame) {
stack.add(frame);
}
public StackFrame[] getStack() {
return this.stack.toArray(new StackFrame[] {});
}
public Function<? super Stream<StackWalker.StackFrame>, StackFrame[]> walkNFrame(int n) {
return s -> { s.limit(n).forEach(this); return this.getStack(); };
}
}

View file

@ -1,32 +0,0 @@
#!/usr/bin/env bash
JAVAC="${JAVAC:-/usr/lib/jvm/java-17-openjdk/bin/javac}"
ANDROID_SDK="${ANDROID_SDK:-${HOME}/Android/Sdk/}"
VERSION="${VERSION:-34.0.0}"
D8="${D8:-${ANDROID_SDK}/build-tools/${VERSION}/d8}"
VERSION_B="${VERSION_B:-$(echo "${VERSION}" | sed 's/\..*//')}"
ANDROID_JAR="${ANDROID_JAR:-${ANDROID_SDK}/platforms/android-${VERSION_B}/android.jar}"
FOLDER=$(dirname "$(realpath $0)")
BUILD_F="${BUILD_F:-${FOLDER}/build}"
OUT_FILE="${OUT_FILE:-${FOLDER}/../theseus_frida/StackConsumer.dex.b64}"
echo "JAVAC = ${JAVAC}"
echo "ANDROID_SDK = ${ANDROID_SDK}"
echo "VERSION = ${VERSION}"
echo "D8 = ${D8}"
# echo "VERSION_B = ${VERSION_B}"
echo "ANDROID_JAR = ${ANDROID_JAR}"
echo "BUILD_F = ${BUILD_F}"
echo "OUT_FILE = ${OUT_FILE}"
rm -r "${BUILD_F}"
mkdir "${BUILD_F}"
"${JAVAC}" -Xlint -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

@ -330,11 +330,6 @@ def collect_runtime(apk: Path, device_name: str, file_storage: Path, output: Tex
with FRIDA_SCRIPT.open("r") as file: with FRIDA_SCRIPT.open("r") as file:
jsscript = file.read() jsscript = file.read()
with STACK_CONSUMER_B64.open("r") as file:
jsscript = jsscript.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

@ -14,6 +14,57 @@ function dump_classloaders() {
}); });
} }
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()}); // recv('dump-class-loaders', function onMessage(msg) {dump_classloaders()});
Java.perform(() => { Java.perform(() => {
@ -44,12 +95,14 @@ Java.perform(() => {
const System = Java.use('java.lang.System'); const System = Java.use('java.lang.System');
const Arrays = Java.use('java.util.Arrays'); const Arrays = Java.use('java.util.Arrays');
/*
const myClassLoader = InMemoryDexClassLoader.$new( const myClassLoader = InMemoryDexClassLoader.$new(
ByteBuffer.wrap(Base64.decode("<PYTHON REPLACE StackConsumer.dex.b64>", Base64.DEFAULT.value)), ByteBuffer.wrap(Base64.decode("<PYTHON REPLACE StackConsumer.dex.b64>", Base64.DEFAULT.value)),
null null
); );
const StackConsumer = Java.ClassFactory.get(myClassLoader).use("theseus.android.StackConsumer"); const StackConsumer = Java.ClassFactory.get(myClassLoader).use("theseus.android.StackConsumer");
*/
const StackConsumer = registerStackConsumer();
const get_stack = function () { const get_stack = function () {
// console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new())); // console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));