Shamrock: 重构收包起,减少拷贝

Signed-off-by: 白池 <whitechi73@outlook.com>
This commit is contained in:
白池 2024-03-10 00:33:26 +08:00
parent 69cdbad643
commit 5637db43be
17 changed files with 224 additions and 6 deletions

View File

@ -91,7 +91,7 @@ class MainActivity : ComponentActivity() {
setContent { setContent {
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
while (true) { while (true) {
delay(15_000) // Delay in milliseconds delay(5_000) // Delay in milliseconds
broadcastToModule { broadcastToModule {
putExtra("__cmd", "switch_status") putExtra("__cmd", "switch_status")
} }

View File

@ -29,6 +29,7 @@ abstract class ModuleHandler {
} }
} }
} }
putExtra("__cmd", cmd)
putExtra("__hash", callbackId) putExtra("__hash", callbackId)
} }
} }

View File

@ -7,9 +7,8 @@ val DEPENDENCY_ANDROIDX = arrayOf(
"androidx.activity:activity-compose:1.7.2", "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_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}" fun room(name: String) = "androidx.room:room-$name:${Versions.roomVersion}"

View File

@ -6,9 +6,13 @@ import com.tencent.mobileqq.app.BusinessObserver;
import com.tencent.mobileqq.app.MessageHandler; import com.tencent.mobileqq.app.MessageHandler;
import com.tencent.qphone.base.remote.ToServiceMsg; import com.tencent.qphone.base.remote.ToServiceMsg;
import java.util.concurrent.ConcurrentHashMap;
import mqq.app.AppRuntime; import mqq.app.AppRuntime;
public abstract class AppInterface extends AppRuntime { public abstract class AppInterface extends AppRuntime {
private final ConcurrentHashMap<String, BusinessHandler> allHandler = new ConcurrentHashMap<>();
public String getCurrentNickname() { public String getCurrentNickname() {
return ""; return "";
} }

View File

@ -13,6 +13,10 @@ public abstract class BaseBusinessHandler extends OidbWrapper {
return null; return null;
} }
public void addBusinessObserver(ToServiceMsg toServiceMsg, BusinessObserver businessObserver, boolean z) {
}
public final <T> T decodePacket(byte[] data, String name, T obj) { public final <T> T decodePacket(byte[] data, String name, T obj) {
UniPacket uniPacket = new UniPacket(true); UniPacket uniPacket = new UniPacket(true);
try { try {
@ -24,6 +28,10 @@ public abstract class BaseBusinessHandler extends OidbWrapper {
} }
} }
public boolean msgCmdFilter(String str) {
return false;
}
protected abstract Set<String> getCommandList(); protected abstract Set<String> getCommandList();
protected abstract Set<String> getPushCommandList(); protected abstract Set<String> getPushCommandList();

View File

@ -8,6 +8,8 @@ public abstract class BusinessHandler extends BaseBusinessHandler {
public BusinessHandler(AppInterface appInterface) { public BusinessHandler(AppInterface appInterface) {
} }
protected abstract Class<? extends BusinessObserver> observerClass();
@Override @Override
public Set<String> getCommandList() { public Set<String> getCommandList() {
return null; return null;

View File

@ -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) {
}
}

View File

@ -66,6 +66,13 @@ public abstract class AppRuntime {
} }
} }
public MobileQQ getApplication() {
return null;
}
public void startServlet(NewIntent newIntent) {
}
public <T extends IRuntimeService> T getRuntimeService(Class<T> cls, String namespace) { public <T extends IRuntimeService> T getRuntimeService(Class<T> cls, String namespace) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }

View File

@ -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<? extends Servlet> cls) {
super(context, cls);
}
public BusinessObserver getObserver() {
return null;
}
public boolean isWithouLogin() {
return false;
}
public void setObserver(BusinessObserver businessObserver) {
}
public void setWithouLogin(boolean z) {
}
}

View File

@ -0,0 +1,7 @@
package moe.fuqiuluo.shamrock.config
object IsInit: ConfigKey<Boolean>() {
override fun name(): String = "is_init"
override fun default(): Boolean = false
}

View File

@ -8,7 +8,7 @@ private val configDir = MobileQQ.getContext().getExternalFilesDir(null)!!
.parentFile!!.resolve("Tencent/Shamrock").also { .parentFile!!.resolve("Tencent/Shamrock").also {
if (!it.exists()) it.mkdirs() if (!it.exists()) it.mkdirs()
} }
private val configFile = configDir.resolve("config.properties") private val configFile = configDir.resolve("config.prop")
private val configKeys = setOf( private val configKeys = setOf(
ActiveRPC, ActiveRPC,
@ -42,6 +42,7 @@ internal object ShamrockConfig: Properties() {
val value = intent.getStringExtra(key.name()) val value = intent.getStringExtra(key.name())
if (value != null) setProperty(key.name(), value) if (value != null) setProperty(key.name(), value)
} }
setProperty(IsInit.name(), "true")
} }
configFile.outputStream().use { configFile.outputStream().use {
store(it, "Shamrock Config ${System.currentTimeMillis()}") store(it, "Shamrock Config ${System.currentTimeMillis()}")

View File

@ -16,6 +16,7 @@ import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.tools.MethodHooker import moe.fuqiuluo.shamrock.tools.MethodHooker
import moe.fuqiuluo.shamrock.tools.hookMethod import moe.fuqiuluo.shamrock.tools.hookMethod
import moe.fuqiuluo.shamrock.utils.PlatformUtils
import moe.fuqiuluo.shamrock.xposed.XposedEntry import moe.fuqiuluo.shamrock.xposed.XposedEntry
import moe.fuqiuluo.shamrock.xposed.loader.LuoClassloader import moe.fuqiuluo.shamrock.xposed.loader.LuoClassloader
import moe.fuqiuluo.shamrock.xposed.loader.NativeLoader import moe.fuqiuluo.shamrock.xposed.loader.NativeLoader
@ -85,7 +86,12 @@ class AntiDetection: IAction {
LogCenter.log("[Shamrock] Shamrock反检测启动失败(env=$env, injected=$injected)", Level.ERROR) LogCenter.log("[Shamrock] Shamrock反检测启动失败(env=$env, injected=$injected)", Level.ERROR)
} else { } else {
XposedEntry.secStaticNativehookInited = true XposedEntry.secStaticNativehookInited = true
LogCenter.log("[Shamrock] Shamrock反检测启动成功: ${antiNativeDetections()}", Level.INFO) if (PlatformUtils.isMainProcess()) {
LogCenter.log(
"[Shamrock] Shamrock反检测启动成功: ${antiNativeDetections()}",
Level.INFO
)
}
} }
} catch (e: Throwable) { } catch (e: Throwable) {
LogCenter.log("[Shamrock] Shamrock反检测启动失败请检查LSPosed版本使用大于100: ${e.message}", Level.ERROR) LogCenter.log("[Shamrock] Shamrock反检测启动失败请检查LSPosed版本使用大于100: ${e.message}", Level.ERROR)

View File

@ -7,6 +7,8 @@ import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.os.Build import android.os.Build
import de.robv.android.xposed.XposedBridge 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.SwitchStatus
import moe.fuqiuluo.shamrock.xposed.actions.interacts.Init import moe.fuqiuluo.shamrock.xposed.actions.interacts.Init
import moe.fuqiuluo.symbols.Process import moe.fuqiuluo.symbols.Process
@ -38,7 +40,12 @@ class DynamicBroadcast: IAction {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
val cmd = intent.getStringExtra("__cmd") ?: "" 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)
}
} }
} }
} }

View File

@ -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)
}
}
}

View File

@ -1,14 +1,38 @@
@file:OptIn(DelicateCoroutinesApi::class)
package moe.fuqiuluo.shamrock.xposed.actions package moe.fuqiuluo.shamrock.xposed.actions
import android.content.Context 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.utils.PlatformUtils
import moe.fuqiuluo.shamrock.xposed.helper.AppTalker
import moe.fuqiuluo.symbols.Process import moe.fuqiuluo.symbols.Process
import moe.fuqiuluo.symbols.XposedHook import moe.fuqiuluo.symbols.XposedHook
import kotlin.system.exitProcess
import kotlin.time.Duration.Companion.seconds
@XposedHook(Process.MAIN, priority = 1) @XposedHook(Process.MAIN, priority = 1)
class PullConfig: IAction { class PullConfig: IAction {
override fun invoke(ctx: Context) { override fun invoke(ctx: Context) {
if (!PlatformUtils.isMainProcess()) return 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("同步配置中...")
} }
} }

View File

@ -24,4 +24,12 @@ internal object AppTalker {
bodyBuilder.invoke(values) bodyBuilder.invoke(values)
talk(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)
}
} }

View File

@ -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<String, MsfPush>()
private val mRespHandler = hashMapOf<Int, MsfResp>()
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)
}
}