collect stack data
This commit is contained in:
parent
7713f3247a
commit
c11101b46a
5 changed files with 175 additions and 37 deletions
2
frida/.gitignore
vendored
2
frida/.gitignore
vendored
|
|
@ -1 +1,3 @@
|
||||||
__pycache__
|
__pycache__
|
||||||
|
theseus_frida/StackConsumer.dex.b64
|
||||||
|
consumer/build
|
||||||
|
|
|
||||||
23
frida/consumer/StackConsumer.java
Normal file
23
frida/consumer/StackConsumer.java
Normal 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
22
frida/consumer/build.sh
Normal 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}"
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue