Skip to content

Commit

Permalink
support okhttp3
Browse files Browse the repository at this point in the history
  • Loading branch information
5ec1cff committed Oct 14, 2023
1 parent 222f709 commit 4832263
Show file tree
Hide file tree
Showing 14 changed files with 1,479 additions and 1 deletion.
168 changes: 168 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
@file:Suppress("UnstableApiUsage")

import java.io.BufferedReader
import java.io.FileInputStream
import java.io.FileReader
import java.io.FileWriter
import java.util.Properties

plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}

val keystorePropertiesFile = rootProject.file("keystore.properties")
Expand Down Expand Up @@ -60,12 +64,176 @@ android {
version = "3.22.1"
}
}
kotlinOptions {
jvmTarget = "11"
}
}

task("generateDefaultOkHttp3Helper") {
group = "StethoX"
doFirst {
runCatching {
val okioTypes = listOf("BufferedSink", "Sink", "BufferedSource", "Okio", "Source", "BufferedSource")
fun String.toOkhttpClass() =
if (this.contains("_")) this.replace("_", ".")
else if (this in okioTypes) "okio.$this"
else "okhttp3.$this"
val f =
project.file("src/main/java/io/github/a13e300/tools/network/okhttp3/OkHttp3Helper.java")
FileWriter(project.file("src/main/java/io/github/a13e300/tools/network/okhttp3/DefaultOkHttp3Helper.java")).use { writer ->
BufferedReader(FileReader(f)).use { reader ->
val cstrContent = StringBuilder()
for (l in reader.lines()) {
if (l.startsWith("package") || l.startsWith("import")) {
writer.write(l)
} else if (l.startsWith("public interface")) {
writer.write("import java.lang.reflect.*;\n")
writer.write("import io.github.a13e300.tools.deobfuscation.NameMap;\n")
writer.write("public class DefaultOkHttp3Helper implements OkHttp3Helper {")
} else {
val trimmed = l.trim()
if (trimmed.startsWith("}") || trimmed.startsWith("//")) continue
val r = Regex("\\s*(.*)\\((.*)\\)(.*);").matchEntire(l) ?: continue
val typeAndName = r.groupValues[1].split(" ")
val arguments = r.groupValues[2]
val exceptions = r.groupValues[3].trim().let {
if (!it.startsWith("throws")) emptyList()
else it.removePrefix("throws").trim().split(Regex("\\s+,\\s+"))
}
var isStatic: Boolean = false
var returnType: String? = null
var className: String? = null
var methodName: String? = null
var functionName: String? = null
var isMethodGetter = false
var isClassGetter = false
var isFieldGetter = false
for (token in typeAndName) {
if (token == "/*static*/") {
isStatic = true
continue
}
if (returnType == null) {
returnType = token
if (token == "Method") isMethodGetter = true
if (token == "Class") isClassGetter = true
if (token == "Field") isFieldGetter = true
} else {
if (isClassGetter) {
className = token.toOkhttpClass()
functionName = token
} else Regex("(.*)_\\d*(.*?)").matchEntire(token)?.let {
className = it.groupValues[1].toOkhttpClass()
methodName = it.groupValues[2]
functionName = token
}
}
}
val argTypesAndNames = arguments.let {
if (isMethodGetter) it.removePrefix("/*").removeSuffix("*/")
else it
}.split(Regex(",\\s*")).mapNotNull { token ->
if (token.isEmpty()) return@mapNotNull null
token.split(" ").let {
it[it.lastIndex - 1].let { t ->
Regex("/\\*(.*)\\*/").matchEntire(t)?.groupValues?.get(1)
?.let { t_ ->
t_.toOkhttpClass() to false
} ?: (t to true)
} to it.last()
}
}
if (isClassGetter) {
writer.write("private Class class_$functionName;\n")
writer.write("@Override public")
writer.write(l.removeSuffix(";") + " {\nreturn class_$functionName;\n}\n")
cstrContent.append("class_$functionName = classLoader.loadClass(nameMap.getClass(\"$className\"));\n")
continue
} else if (isFieldGetter) {
writer.write("private Field field_$functionName;\n")
writer.write("@Override public")
writer.write(l.removeSuffix(";") + " {\nreturn field_$functionName;\n}\n")
cstrContent.append("field_$functionName = classLoader.loadClass(nameMap.getClass(\"$className\")).getDeclaredField(nameMap.getField(\"$className\", \"$methodName\"));\n")
cstrContent.append("field_$functionName.setAccessible(true);\n")
continue
}
writer.write("private Method method_$functionName;\n")
writer.write("@Override public ")
writer.write(l.removeSuffix(";") + " {\n")
if (isMethodGetter) {
writer.write("return method_$functionName;\n}")
} else {
writer.write("try {\n${if (returnType == "void") "" else "return ($returnType)"}method_$functionName.invoke(")
val argList = mutableListOf<String>()
if (isStatic) argList.add("null")
for ((_, name) in argTypesAndNames) {
argList.add(name)
}
writer.write(argList.joinToString(", "))
writer.write(");\n}")
if (exceptions.isNotEmpty()) {
writer.write("catch (InvocationTargetException ex) {\n")
writer.write(exceptions.map { ex ->
"if (ex.getCause() instanceof $ex) { throw ($ex) ex.getCause(); }\n"
}.joinToString(" else "))
writer.write(" else throw new RuntimeException(ex);\n}")
}
writer.write("catch (Throwable t) { throw new RuntimeException(t); }\n}")
}
cstrContent.append("method_$functionName = classLoader.loadClass(nameMap.getClass(\"$className\"))")
.append(".getDeclaredMethod(nameMap.getMethod(\"$className\", \"$methodName\"")
.apply {
var first = true
for ((t, _) in argTypesAndNames) {
if (!isStatic && first) {
first = false
continue
}
val (type, _) = t
append(", \"")
if (type == "String") append("java.lang.String")
else append(type)
append("\"")
}
append(")")
}
var first = true
for ((t, _) in argTypesAndNames) {
val (type, known) = t
if (!isStatic && first) {
first = false
continue
}
cstrContent.append(",")
if (known)
cstrContent.append("$type.class")
else
cstrContent.append("classLoader.loadClass(\"$type\")")
}
cstrContent.append(");\nmethod_$functionName.setAccessible(true);\n")
}
writer.write("\n")
}
writer.write("")
writer.write("public DefaultOkHttp3Helper(ClassLoader classLoader, NameMap nameMap) {\n")
writer.write("try {\n")
writer.write(cstrContent.toString())
writer.write("} catch (Throwable t) {\nthrow new RuntimeException(t);\n}\n}\n}")
}
}
}.onFailure { it.printStackTrace() }
}
}

dependencies {
implementation("androidx.annotation:annotation:1.7.0")
implementation("androidx.core:core-ktx:+")
compileOnly("de.robv.android.xposed:api:82")
compileOnly(project(":hidden-api"))
implementation("com.github.5ec1cff.stetho:stetho:1.0-alpha-1")
implementation("com.github.5ec1cff.stetho:stetho-js-rhino:1.0-alpha-1")
implementation("com.github.5ec1cff.stetho:stetho-urlconnection:1.0-alpha-1")
implementation("org.mozilla:rhino:1.7.15-SNAPSHOT")
implementation("com.linkedin.dexmaker:dexmaker:2.28.3")
implementation("org.luckypray:dexkit:2.0.0-rc4")
}
38 changes: 38 additions & 0 deletions app/src/main/java/io/github/a13e300/tools/DexKitWrapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package io.github.a13e300.tools;

import androidx.annotation.NonNull;

import org.luckypray.dexkit.DexKitBridge;

import java.util.Objects;

public class DexKitWrapper {

static {
try {
System.loadLibrary("dexkit");
} catch (Throwable t) {
Logger.e("failed to load dexkit", t);
}
}

@NonNull
public static DexKitBridge create(String apkPath) {
return Objects.requireNonNull(DexKitBridge.create(apkPath), "dexkit failed to create");
}

@NonNull
public static DexKitBridge create(ClassLoader cl) {
return Objects.requireNonNull(DexKitBridge.create(cl, true), "dexkit failed to create");
}

@NonNull
public static DexKitBridge create(ClassLoader cl, boolean useMemory) {
return Objects.requireNonNull(DexKitBridge.create(cl, useMemory), "dexkit failed to create");
}

@NonNull
public static DexKitBridge create(byte[][] dex) {
return Objects.requireNonNull(DexKitBridge.create(dex), "dexkit failed to create");
}
}
73 changes: 73 additions & 0 deletions app/src/main/java/io/github/a13e300/tools/DexUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package io.github.a13e300.tools;


import android.util.Log;

import com.android.dx.DexMaker;
import com.android.dx.TypeId;

import java.lang.reflect.Modifier;
import java.nio.ByteBuffer;

import dalvik.system.InMemoryDexClassLoader;

public class DexUtils {
public static void log(String msg) {
Logger.d("DexUtils log: " + msg);
}
public static Object make(ClassLoader classLoader, String name, String superName) {
var maker = new DexMaker();
var superType = TypeId.get(superName); // LNeverCall$AB;
var myType = TypeId.get(name); // LMyAB;
maker.declare(myType, "Generated", Modifier.PUBLIC, superType);
var printMethod = myType.getMethod(TypeId.VOID, "print", TypeId.STRING);
var cstr = maker.declare(myType.getConstructor(), Modifier.PUBLIC);
var superCstr = superType.getConstructor();
cstr.invokeDirect(superCstr, null, cstr.getThis(myType));
cstr.returnVoid();
var code = maker.declare(printMethod, Modifier.PUBLIC);
var tag = code.newLocal(TypeId.STRING);
code.loadConstant(tag, "DexUtils");
var p0 = code.getParameter(0, TypeId.STRING);
var logI = TypeId.get(Log.class).getMethod(TypeId.INT, "i", TypeId.STRING, TypeId.STRING);
var myLog = TypeId.get(DexUtils.class).getMethod(TypeId.VOID, "log", TypeId.STRING);
code.invokeStatic(logI, null, tag, p0);
code.invokeStatic(myLog, null, p0);
code.returnVoid();
var dex = maker.generate();
try {
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O) return null;
var loader = new InMemoryDexClassLoader(ByteBuffer.wrap(dex), new HybridClassLoader(classLoader));
var clazz = loader.loadClass("MyAB");
Logger.d("class loader=" + clazz.getClassLoader());
var superClass = classLoader.loadClass("NeverCall$AB");
var inst = clazz.newInstance();
var m = superClass.getDeclaredMethod("call", superClass, String.class);
m.setAccessible(true);
m.invoke(null, inst, "test");
return inst;
} catch (Throwable t) {
Logger.e("dexutils load and call", t);
}
return null;
}

static class HybridClassLoader extends ClassLoader {
private final ClassLoader mBase;
public HybridClassLoader(ClassLoader parent) {
mBase = parent;
}

@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (name.startsWith("io.github.a13e300.tools")) {
return getClass().getClassLoader().loadClass(name);
}
try {
return mBase.loadClass(name);
} catch (ClassNotFoundException e) {
return super.loadClass(name, resolve);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import com.facebook.stetho.Stetho;
import com.facebook.stetho.rhino.JsRuntimeReplFactoryBuilder;

import org.mozilla.javascript.BaseFunction;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;

import java.lang.reflect.InvocationTargetException;
Expand All @@ -28,6 +30,7 @@
import io.github.a13e300.tools.objects.GetStackTraceFunction;
import io.github.a13e300.tools.objects.HookFunction;
import io.github.a13e300.tools.objects.HookParam;
import io.github.a13e300.tools.objects.OkHttpInterceptorObject;
import io.github.a13e300.tools.objects.PrintStackTraceFunction;
import io.github.a13e300.tools.objects.RunOnHandlerFunction;
import io.github.a13e300.tools.objects.UnhookFunction;
Expand Down Expand Up @@ -82,7 +85,9 @@ private synchronized void initializeStetho(Context context) throws InterruptedEx
ScriptableObject.defineClass(scope, HookFunction.class);
ScriptableObject.defineClass(scope, UnhookFunction.class);
ScriptableObject.defineClass(scope, HookParam.class);
ScriptableObject.defineClass(scope, OkHttpInterceptorObject.class);
scope.defineProperty("hook", new HookFunction(scope), ScriptableObject.DONTENUM | ScriptableObject.CONST);
scope.defineProperty("okhttp3", new OkHttpInterceptorObject(scope), ScriptableObject.DONTENUM | ScriptableObject.CONST);
synchronized (StethoxAppInterceptor.this) {
if (mWaiting) {
var method = StethoxAppInterceptor.class.getDeclaredMethod("cont", ScriptableObject.class);
Expand All @@ -99,6 +104,19 @@ private synchronized void initializeStetho(Context context) throws InterruptedEx
})
.onFinalize(scope -> {
((HookFunction) ScriptableObject.getProperty(scope, "hook")).clearHooks();
((OkHttpInterceptorObject) ScriptableObject.getProperty(scope, "okhttp3")).stop(true);
})
// .importClass(DexUtils.class)
// .importClass(StethoOkHttp3ProxyInterceptor.class)
// .importClass(MutableNameMap.class)
// .importClass(DexKitWrapper.class)
// .importPackage("org.luckypray.dexkit.query")
// .importPackage("org.luckypray.dexkit.query.matchers")
.addFunction("MYCL", new BaseFunction() {
@Override
public Object call(org.mozilla.javascript.Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
return StethoxAppInterceptor.class.getClassLoader();
}
})
.build()
).finish()
Expand Down
Loading

0 comments on commit 4832263

Please sign in to comment.