From 5637db43bece6d2ef670d74a97903e9ad1d2866e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E6=B1=A0?= Date: Sun, 10 Mar 2024 00:33:26 +0800 Subject: [PATCH] =?UTF-8?q?`Shamrock`:=20=E9=87=8D=E6=9E=84=E6=94=B6?= =?UTF-8?q?=E5=8C=85=E8=B5=B7=EF=BC=8C=E5=87=8F=E5=B0=91=E6=8B=B7=E8=B4=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 白池 --- .../moe/fuqiuluo/shamrock/MainActivity.kt | 2 +- .../ui/service/handlers/ModuleHandler.kt | 1 + buildSrc/src/main/kotlin/Dependencies.kt | 3 +- .../com/tencent/common/app/AppInterface.java | 4 ++ .../mobileqq/app/BaseBusinessHandler.java | 8 +++ .../tencent/mobileqq/app/BusinessHandler.java | 2 + .../mobileqq/msf/sdk/MsfMessagePair.java | 18 +++++++ .../src/main/java/mqq/app/AppRuntime.java | 7 +++ .../src/main/java/mqq/app/NewIntent.java | 29 +++++++++++ .../moe/fuqiuluo/shamrock/config/IsInit.kt | 7 +++ .../shamrock/config/ShamrockConfig.kt | 3 +- .../shamrock/xposed/actions/AntiDetection.kt | 8 ++- .../xposed/actions/DynamicBroadcast.kt | 9 +++- .../shamrock/xposed/actions/PatchMsfCore.kt | 45 ++++++++++++++++ .../shamrock/xposed/actions/PullConfig.kt | 24 +++++++++ .../shamrock/xposed/helper/AppTalker.kt | 8 +++ .../java/qq/service/internals/MSFHandler.kt | 52 +++++++++++++++++++ 17 files changed, 224 insertions(+), 6 deletions(-) create mode 100644 qqinterface/src/main/java/com/tencent/mobileqq/msf/sdk/MsfMessagePair.java create mode 100644 qqinterface/src/main/java/mqq/app/NewIntent.java create mode 100644 xposed/src/main/java/moe/fuqiuluo/shamrock/config/IsInit.kt create mode 100644 xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/actions/PatchMsfCore.kt create mode 100644 xposed/src/main/java/qq/service/internals/MSFHandler.kt diff --git a/app/src/main/java/moe/fuqiuluo/shamrock/MainActivity.kt b/app/src/main/java/moe/fuqiuluo/shamrock/MainActivity.kt index 83abc6a..52f4e80 100644 --- a/app/src/main/java/moe/fuqiuluo/shamrock/MainActivity.kt +++ b/app/src/main/java/moe/fuqiuluo/shamrock/MainActivity.kt @@ -91,7 +91,7 @@ class MainActivity : ComponentActivity() { setContent { LaunchedEffect(Unit) { while (true) { - delay(15_000) // Delay in milliseconds + delay(5_000) // Delay in milliseconds broadcastToModule { putExtra("__cmd", "switch_status") } diff --git a/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/handlers/ModuleHandler.kt b/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/handlers/ModuleHandler.kt index 547b074..c13b655 100644 --- a/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/handlers/ModuleHandler.kt +++ b/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/handlers/ModuleHandler.kt @@ -29,6 +29,7 @@ abstract class ModuleHandler { } } } + putExtra("__cmd", cmd) putExtra("__hash", callbackId) } } diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 13e0d4c..9717f3b 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -7,9 +7,8 @@ val DEPENDENCY_ANDROIDX = arrayOf( "androidx.activity:activity-compose:1.7.2", ) -const val DEPENDENCY_JSON5K = "io.github.xn32:json5k:0.3.0" + const val DEPENDENCY_PROTOBUF = "com.google.protobuf:protobuf-java:3.24.0" -const val DEPENDENCY_JAVA_WEBSOCKET = "org.java-websocket:Java-WebSocket:1.5.4" fun room(name: String) = "androidx.room:room-$name:${Versions.roomVersion}" diff --git a/qqinterface/src/main/java/com/tencent/common/app/AppInterface.java b/qqinterface/src/main/java/com/tencent/common/app/AppInterface.java index 694bd70..80a3d99 100644 --- a/qqinterface/src/main/java/com/tencent/common/app/AppInterface.java +++ b/qqinterface/src/main/java/com/tencent/common/app/AppInterface.java @@ -6,9 +6,13 @@ import com.tencent.mobileqq.app.BusinessObserver; import com.tencent.mobileqq.app.MessageHandler; import com.tencent.qphone.base.remote.ToServiceMsg; +import java.util.concurrent.ConcurrentHashMap; + import mqq.app.AppRuntime; public abstract class AppInterface extends AppRuntime { + private final ConcurrentHashMap allHandler = new ConcurrentHashMap<>(); + public String getCurrentNickname() { return ""; } diff --git a/qqinterface/src/main/java/com/tencent/mobileqq/app/BaseBusinessHandler.java b/qqinterface/src/main/java/com/tencent/mobileqq/app/BaseBusinessHandler.java index 4287a5a..a5292b0 100644 --- a/qqinterface/src/main/java/com/tencent/mobileqq/app/BaseBusinessHandler.java +++ b/qqinterface/src/main/java/com/tencent/mobileqq/app/BaseBusinessHandler.java @@ -13,6 +13,10 @@ public abstract class BaseBusinessHandler extends OidbWrapper { return null; } + public void addBusinessObserver(ToServiceMsg toServiceMsg, BusinessObserver businessObserver, boolean z) { + + } + public final T decodePacket(byte[] data, String name, T obj) { UniPacket uniPacket = new UniPacket(true); try { @@ -24,6 +28,10 @@ public abstract class BaseBusinessHandler extends OidbWrapper { } } + public boolean msgCmdFilter(String str) { + return false; + } + protected abstract Set getCommandList(); protected abstract Set getPushCommandList(); diff --git a/qqinterface/src/main/java/com/tencent/mobileqq/app/BusinessHandler.java b/qqinterface/src/main/java/com/tencent/mobileqq/app/BusinessHandler.java index 970dcec..74cde56 100644 --- a/qqinterface/src/main/java/com/tencent/mobileqq/app/BusinessHandler.java +++ b/qqinterface/src/main/java/com/tencent/mobileqq/app/BusinessHandler.java @@ -8,6 +8,8 @@ public abstract class BusinessHandler extends BaseBusinessHandler { public BusinessHandler(AppInterface appInterface) { } + protected abstract Class observerClass(); + @Override public Set getCommandList() { return null; diff --git a/qqinterface/src/main/java/com/tencent/mobileqq/msf/sdk/MsfMessagePair.java b/qqinterface/src/main/java/com/tencent/mobileqq/msf/sdk/MsfMessagePair.java new file mode 100644 index 0000000..e6c82db --- /dev/null +++ b/qqinterface/src/main/java/com/tencent/mobileqq/msf/sdk/MsfMessagePair.java @@ -0,0 +1,18 @@ +package com.tencent.mobileqq.msf.sdk; + +import com.tencent.qphone.base.remote.FromServiceMsg; +import com.tencent.qphone.base.remote.ToServiceMsg; + +public class MsfMessagePair { + public FromServiceMsg fromServiceMsg; + public String sendProcess; + public ToServiceMsg toServiceMsg; + + public MsfMessagePair(String str, ToServiceMsg toServiceMsg, FromServiceMsg fromServiceMsg) { + + } + + public MsfMessagePair(ToServiceMsg toServiceMsg, FromServiceMsg fromServiceMsg) { + + } +} \ No newline at end of file diff --git a/qqinterface/src/main/java/mqq/app/AppRuntime.java b/qqinterface/src/main/java/mqq/app/AppRuntime.java index 5562807..1693142 100644 --- a/qqinterface/src/main/java/mqq/app/AppRuntime.java +++ b/qqinterface/src/main/java/mqq/app/AppRuntime.java @@ -66,6 +66,13 @@ public abstract class AppRuntime { } } + public MobileQQ getApplication() { + return null; + } + + public void startServlet(NewIntent newIntent) { + } + public T getRuntimeService(Class cls, String namespace) { throw new UnsupportedOperationException(); } diff --git a/qqinterface/src/main/java/mqq/app/NewIntent.java b/qqinterface/src/main/java/mqq/app/NewIntent.java new file mode 100644 index 0000000..91f8577 --- /dev/null +++ b/qqinterface/src/main/java/mqq/app/NewIntent.java @@ -0,0 +1,29 @@ +package mqq.app; + +import android.content.Context; +import android.content.Intent; + +import com.tencent.mobileqq.app.BusinessObserver; + +public class NewIntent extends Intent { + public boolean runNow; + + public NewIntent(Context context, Class cls) { + super(context, cls); + } + + public BusinessObserver getObserver() { + return null; + } + + public boolean isWithouLogin() { + return false; + } + + public void setObserver(BusinessObserver businessObserver) { + + } + + public void setWithouLogin(boolean z) { + } +} diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/config/IsInit.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/config/IsInit.kt new file mode 100644 index 0000000..02adf33 --- /dev/null +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/config/IsInit.kt @@ -0,0 +1,7 @@ +package moe.fuqiuluo.shamrock.config + +object IsInit: ConfigKey() { + override fun name(): String = "is_init" + + override fun default(): Boolean = false +} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/config/ShamrockConfig.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/config/ShamrockConfig.kt index f03c5f8..4866b18 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/config/ShamrockConfig.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/config/ShamrockConfig.kt @@ -8,7 +8,7 @@ private val configDir = MobileQQ.getContext().getExternalFilesDir(null)!! .parentFile!!.resolve("Tencent/Shamrock").also { if (!it.exists()) it.mkdirs() } -private val configFile = configDir.resolve("config.properties") +private val configFile = configDir.resolve("config.prop") private val configKeys = setOf( ActiveRPC, @@ -42,6 +42,7 @@ internal object ShamrockConfig: Properties() { val value = intent.getStringExtra(key.name()) if (value != null) setProperty(key.name(), value) } + setProperty(IsInit.name(), "true") } configFile.outputStream().use { store(it, "Shamrock Config ${System.currentTimeMillis()}") diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/actions/AntiDetection.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/actions/AntiDetection.kt index d32c221..878cc10 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/actions/AntiDetection.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/actions/AntiDetection.kt @@ -16,6 +16,7 @@ import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.tools.MethodHooker import moe.fuqiuluo.shamrock.tools.hookMethod +import moe.fuqiuluo.shamrock.utils.PlatformUtils import moe.fuqiuluo.shamrock.xposed.XposedEntry import moe.fuqiuluo.shamrock.xposed.loader.LuoClassloader import moe.fuqiuluo.shamrock.xposed.loader.NativeLoader @@ -85,7 +86,12 @@ class AntiDetection: IAction { LogCenter.log("[Shamrock] Shamrock反检测启动失败(env=$env, injected=$injected)", Level.ERROR) } else { XposedEntry.secStaticNativehookInited = true - LogCenter.log("[Shamrock] Shamrock反检测启动成功: ${antiNativeDetections()}", Level.INFO) + if (PlatformUtils.isMainProcess()) { + LogCenter.log( + "[Shamrock] Shamrock反检测启动成功: ${antiNativeDetections()}", + Level.INFO + ) + } } } catch (e: Throwable) { LogCenter.log("[Shamrock] Shamrock反检测启动失败,请检查LSPosed版本使用大于100: ${e.message}", Level.ERROR) diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/actions/DynamicBroadcast.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/actions/DynamicBroadcast.kt index 20e5292..98e5ac8 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/actions/DynamicBroadcast.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/actions/DynamicBroadcast.kt @@ -7,6 +7,8 @@ import android.content.Intent import android.content.IntentFilter import android.os.Build import de.robv.android.xposed.XposedBridge +import moe.fuqiuluo.shamrock.helper.Level +import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.xposed.actions.interacts.SwitchStatus import moe.fuqiuluo.shamrock.xposed.actions.interacts.Init import moe.fuqiuluo.symbols.Process @@ -38,7 +40,12 @@ class DynamicBroadcast: IAction { override fun onReceive(context: Context, intent: Intent) { val cmd = intent.getStringExtra("__cmd") ?: "" - handlers[cmd]?.invoke(intent) + val handler = handlers[cmd] + if (handler == null) { + LogCenter.log("DynamicReceiver.onReceive: unknown cmd=$cmd", Level.ERROR) + } else { + handler(intent) + } } } } \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/actions/PatchMsfCore.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/actions/PatchMsfCore.kt new file mode 100644 index 0000000..fcf823a --- /dev/null +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/actions/PatchMsfCore.kt @@ -0,0 +1,45 @@ +@file:Suppress("UNCHECKED_CAST", "UNUSED_VARIABLE", "LocalVariableName") +package moe.fuqiuluo.shamrock.xposed.actions + +import android.content.Context +import com.tencent.common.app.AppInterface +import com.tencent.mobileqq.msf.sdk.MsfMessagePair +import moe.fuqiuluo.shamrock.helper.Level +import moe.fuqiuluo.shamrock.helper.LogCenter +import moe.fuqiuluo.shamrock.tools.hookMethod +import moe.fuqiuluo.shamrock.xposed.loader.LuoClassloader +import moe.fuqiuluo.symbols.XposedHook +import qq.service.QQInterfaces +import qq.service.internals.MSFHandler.onPush +import qq.service.internals.MSFHandler.onResp + + +@XposedHook(priority = 10) +class PatchMsfCore: IAction { + override fun invoke(ctx: Context) { + val app = QQInterfaces.app + require(app is AppInterface) { "QQInterface.app must be AppInterface" } + + runCatching { + val MSFRespHandleTask = LuoClassloader.load("mqq.app.msghandle.MSFRespHandleTask") + if (MSFRespHandleTask == null) { + LogCenter.log("无法注入MSFRespHandleTask!", Level.ERROR) + } else { + val msfPair = MSFRespHandleTask.declaredFields.first { + it.type == MsfMessagePair::class.java + } + msfPair.isAccessible = true + MSFRespHandleTask.hookMethod("run").before { + val pair = msfPair.get(it.thisObject) as MsfMessagePair + if (pair.toServiceMsg == null) { + onPush(pair.fromServiceMsg) + } else { + onResp(pair.toServiceMsg, pair.fromServiceMsg) + } + } + } + }.onFailure { + LogCenter.log(it.stackTraceToString(), Level.ERROR) + } + } +} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/actions/PullConfig.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/actions/PullConfig.kt index 609ada6..34b0081 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/actions/PullConfig.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/actions/PullConfig.kt @@ -1,14 +1,38 @@ +@file:OptIn(DelicateCoroutinesApi::class) package moe.fuqiuluo.shamrock.xposed.actions import android.content.Context +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import moe.fuqiuluo.shamrock.config.IsInit +import moe.fuqiuluo.shamrock.config.ShamrockConfig +import moe.fuqiuluo.shamrock.tools.toast import moe.fuqiuluo.shamrock.utils.PlatformUtils +import moe.fuqiuluo.shamrock.xposed.helper.AppTalker import moe.fuqiuluo.symbols.Process import moe.fuqiuluo.symbols.XposedHook +import kotlin.system.exitProcess +import kotlin.time.Duration.Companion.seconds @XposedHook(Process.MAIN, priority = 1) class PullConfig: IAction { override fun invoke(ctx: Context) { if (!PlatformUtils.isMainProcess()) return + val isInit = ShamrockConfig[IsInit] + AppTalker.talk("init", onFailure = { + if (isInit) { + ctx.toast("Shamrock主进程未启动,将不会同步配置!") + } else { + ctx.toast("Shamrock主进程未启动,初始化失败!") + GlobalScope.launch { + delay(3.seconds) + exitProcess(1) + } + } + }) + ctx.toast("同步配置中...") } } \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/AppTalker.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/AppTalker.kt index 63374e4..7803197 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/AppTalker.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/AppTalker.kt @@ -24,4 +24,12 @@ internal object AppTalker { bodyBuilder.invoke(values) talk(values) } + + fun talk(action: String, onFailure: ((Throwable) -> Unit)? = null, bodyBuilder: ContentValues.() -> Unit = {}) { + val values = ContentValues() + values.put("__cmd", action) + values.put("__hash", 0) + bodyBuilder.invoke(values) + talk(values, onFailure) + } } \ No newline at end of file diff --git a/xposed/src/main/java/qq/service/internals/MSFHandler.kt b/xposed/src/main/java/qq/service/internals/MSFHandler.kt new file mode 100644 index 0000000..900ecf6 --- /dev/null +++ b/xposed/src/main/java/qq/service/internals/MSFHandler.kt @@ -0,0 +1,52 @@ +package qq.service.internals + +import com.tencent.qphone.base.remote.FromServiceMsg +import com.tencent.qphone.base.remote.ToServiceMsg +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock + +typealias MsfPush = (FromServiceMsg) -> Unit +typealias MsfResp = (ToServiceMsg, FromServiceMsg) -> Unit + +internal object MSFHandler { + private val mPushHandlers = hashMapOf() + private val mRespHandler = hashMapOf() + private val mPushLock = Mutex() + private val mRespLock = Mutex() + + suspend fun registerPush(cmd: String, push: MsfPush) { + mPushLock.withLock { + mPushHandlers[cmd] = push + } + } + + suspend fun unregisterPush(cmd: String) { + mPushLock.withLock { + mPushHandlers.remove(cmd) + } + } + + suspend fun registerResp(cmd: Int, resp: MsfResp) { + mRespLock.withLock { + mRespHandler[cmd] = resp + } + } + + suspend fun unregisterResp(cmd: Int) { + mRespLock.withLock { + mRespHandler.remove(cmd) + } + } + + fun onPush(fromServiceMsg: FromServiceMsg) { + val cmd = fromServiceMsg.serviceCmd + val push = mPushHandlers[cmd] + push?.invoke(fromServiceMsg) + } + + fun onResp(toServiceMsg: ToServiceMsg, fromServiceMsg: FromServiceMsg) { + val cmd = toServiceMsg.getAttribute("respkey") as Int + val resp = mRespHandler[cmd] + resp?.invoke(toServiceMsg, fromServiceMsg) + } +} \ No newline at end of file