Skip to content

Commit

Permalink
support hook whole class & fix getClassLoader of HookFunction & add fst
Browse files Browse the repository at this point in the history
  • Loading branch information
5ec1cff committed Jun 12, 2024
1 parent 05dc9e2 commit 4e792ba
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import io.github.a13e300.tools.objects.FindStackTraceFunction;
import io.github.a13e300.tools.objects.GetStackTraceFunction;
import io.github.a13e300.tools.objects.HookFunction;
import io.github.a13e300.tools.objects.HookParam;
Expand All @@ -40,6 +41,7 @@ public class StethoxAppInterceptor implements IXposedHookZygoteInit {

private boolean initialized = false;
private boolean mWaiting = false;
public static ClassLoader mClassLoader = null;

private synchronized void cont(ScriptableObject ignore) {
if (mWaiting) {
Expand Down Expand Up @@ -71,12 +73,18 @@ private synchronized void initializeStetho(Context context) throws InterruptedEx
} catch (IllegalArgumentException e) {
Logger.e("failed to find suspend provider, please ensure module process without restriction", e);
}
try {
mClassLoader = context.getClassLoader();
} catch (Throwable t) {
Logger.e("failed to get classloader for context", t);
}
Stetho.initialize(Stetho.newInitializerBuilder(context)
.enableWebKitInspector(
() -> new Stetho.DefaultInspectorModulesBuilder(context).runtimeRepl(
new JsRuntimeReplFactoryBuilder(context)
.addFunction("getStackTrace", new GetStackTraceFunction())
.addFunction("printStackTrace", new PrintStackTraceFunction())
.addFunction("findStackTrace", new FindStackTraceFunction())
.addFunction("runOnUiThread", new RunOnHandlerFunction(new Handler(Looper.getMainLooper())))
.addFunction("runOnHandler", new RunOnHandlerFunction())
.addFunction("JArray", new JArrayFunction(null))
Expand All @@ -95,7 +103,9 @@ private synchronized void initializeStetho(Context context) throws InterruptedEx
.addVariable("boolean", boolean.class)
.addVariable("char", char.class)
.importPackage("java.lang")
.onInitScope(scope -> {
.addVariable("xposed_bridge", XposedBridge.class)
.addVariable("xposed_helper", XposedHelpers.class)
.onInitScope((jsContext, scope) -> {
try {
scope.defineProperty("activities", null, Utils.class.getDeclaredMethod("getActivities", ScriptableObject.class), null, ScriptableObject.READONLY);
scope.defineProperty("current", null, Utils.class.getDeclaredMethod("getCurrentActivity", ScriptableObject.class), null, ScriptableObject.READONLY);
Expand All @@ -112,6 +122,9 @@ private synchronized void initializeStetho(Context context) throws InterruptedEx
scope.defineProperty("cont", StethoxAppInterceptor.this, method, null, ScriptableObject.READONLY);
}
}
jsContext.evaluateString(scope,
"let st, pst, fst; st = pst = printStackTrace; fst = findStackTrace;",
"initializer", 1, null);
} catch (NoSuchMethodException |
InvocationTargetException |
IllegalAccessException |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package io.github.a13e300.tools.objects;

import android.app.Activity;
import android.util.Log;
import android.view.View;
import android.view.Window;

import com.facebook.stetho.rhino.JsConsole;

import org.mozilla.javascript.BaseFunction;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.Wrapper;

import de.robv.android.xposed.XposedHelpers;

public class FindStackTraceFunction extends BaseFunction {
@Override
public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
if (args.length == 1) {
var obj = args[0];
Throwable t;
if (obj instanceof Wrapper) {
obj = ((Wrapper) obj).unwrap();
}
if (obj instanceof Throwable) {
t = (Throwable) obj;
} else {
if (obj instanceof Activity) {
obj = ((Activity) obj).getWindow();
}
if (obj instanceof Window) {
obj = ((Window) obj).getDecorView();
}
if (obj instanceof View) {
t = (Throwable) XposedHelpers.getObjectField(
XposedHelpers.callMethod(obj, "getViewRootImpl"),
"mLocation"
);
} else {
throw new IllegalArgumentException("required a view or throwable!");
}
}
JsConsole.fromScope(scope).log(Log.getStackTraceString(t));
return null;
}
throw new IllegalArgumentException("must be 1 view or throwable arg !");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import static io.github.a13e300.tools.Utils.enterJsContext;

import android.app.AndroidAppHelper;
import android.util.Log;

import com.facebook.stetho.rhino.JsConsole;
Expand All @@ -24,12 +23,14 @@
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XC_MethodReplacement;
import de.robv.android.xposed.XposedBridge;
import io.github.a13e300.tools.NativeUtils;
import io.github.a13e300.tools.StethoxAppInterceptor;
import io.github.a13e300.tools.Utils;

public class HookFunction extends BaseFunction {
Expand Down Expand Up @@ -59,9 +60,9 @@ public String getClassName() {

synchronized ClassLoader getClassLoader() {
if (mClassLoader == null) {
var app = AndroidAppHelper.currentApplication();
if (app == null) return ClassLoader.getSystemClassLoader();
mClassLoader = app.getClassLoader();
ClassLoader cl = StethoxAppInterceptor.mClassLoader;
if (cl == null) return ClassLoader.getSystemClassLoader();
mClassLoader = cl;
}
return mClassLoader;
}
Expand Down Expand Up @@ -113,7 +114,9 @@ private UnhookFunction hook(Context cx, Scriptable scope, Object[] args) {
if (pos + 1 == args.length - 2 && args[pos + 1] == null) {
emptySig = true;
}
} else throw new IllegalArgumentException("method name is required");
} else {
methodName = null;
}
pos++;
Class<?>[] methodSig;
if (!emptySig) {
Expand All @@ -139,7 +142,38 @@ private UnhookFunction hook(Context cx, Scriptable scope, Object[] args) {
methodSig = new Class[0];
}
try {
if ("<init>".equals(methodName)) {
if (methodName == null) {
if (methodSig.length != 0) throw new IllegalArgumentException("should not specify method signature!");
hookMembers = List.of(hookClass.getDeclaredMethods());
} else if (methodName.startsWith("@")) {
if (methodName.equals("@")) throw new IllegalArgumentException("must contain at least one of: method,all,ALL,constructor,init,public,instance");
var queryArgs = methodName.substring(1).split(",");
var isPublic = false;
var allMethod = false;
var allConstructor = false;
var isInstance = false;
for (var c: queryArgs) {
if ("all".equals(c) || "method".equals(c)) allMethod = true;
else if ("ALL".equals(c)) allMethod = allConstructor = true;
else if ("constructor".equals(c) || "init".equals(c)) allConstructor = true;
else if ("public".equals(c)) allMethod = isPublic = true;
else if ("instance".equals(c)) allMethod = isInstance = true;
else throw new IllegalArgumentException("must contain at least one of: method,all,ALL,constructor,init,public,instance");
}
var fIsPublic = isPublic;
var fIsInstance = isInstance;
if (methodSig.length != 0) throw new IllegalArgumentException("should not specify method signature!");
var result = new ArrayList<Member>();
Consumer<Member> consumer = (it) -> {
var match = true;
if (fIsPublic) match = Modifier.isPublic(it.getModifiers());
if (fIsInstance) match &= !Modifier.isStatic(it.getModifiers());
if (match) result.add(it);
};
if (allMethod) Arrays.stream(hookClass.getDeclaredMethods()).forEach(consumer);
if (allConstructor) Arrays.stream(hookClass.getDeclaredConstructors()).forEach(consumer);
hookMembers = result;
} else if ("<init>".equals(methodName)) {
if (methodSig.length == 0 && !emptySig) {
hookMembers = List.of(hookClass.getDeclaredConstructors());
} else {
Expand Down Expand Up @@ -275,12 +309,14 @@ synchronized void setClassLoader(ClassLoader cl) {
+ " When you disconnected from the console, all hooks are automatically removed\n"
+ " Utils:\n"
+ " getStackTrace()\n"
+ " printStackTrace()\n"
+ " printStackTrace() / pst()\n"
+ " findStackTrace(obj) / fst(obj): find stack trace and print, such as View\n"
+ " hook.findClass(String[, Classloader])\n"
+ " hook.setClassLoader / getClassLoader\n"
+ " hook.getClassLoaders (get an array of all classloaders)\n"
+ " runOnHandler(callback, handler) & runOnUiThread(callback)\n"
+ " trace() / traces(): parameters like hook, but without callback, the hook will print corresponding information automatically (traces contains stack trace)";
+ " trace() / traces(): parameters like hook, but without callback, the hook will print corresponding information automatically (traces contains stack trace)\n"
+ " deoptimizeMethod(method)";

@JSFunction
public static String toString(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
Expand Down Expand Up @@ -380,4 +416,9 @@ protected void afterHookedMethod(MethodHookParam param) throws Throwable {
console.info("start tracing " + unhook.mUnhooks.size() + " methods (stackTrace=" + stack + ")");
return unhook;
}

@JSFunction
public static void deoptimizeMethod(Context cx, Scriptable thisObj, Object[] args, Function funObj) throws Throwable {
XposedBridge.class.getDeclaredMethod("deoptimizeMethod", Member.class).invoke(null, args[0]);
}
}

0 comments on commit 4e792ba

Please sign in to comment.