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 {
LaunchedEffect(Unit) {
while (true) {
delay(15_000) // Delay in milliseconds
delay(5_000) // Delay in milliseconds
broadcastToModule {
putExtra("__cmd", "switch_status")
}

View File

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

View File

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

View File

@ -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<String, BusinessHandler> allHandler = new ConcurrentHashMap<>();
public String getCurrentNickname() {
return "";
}

View File

@ -13,6 +13,10 @@ public abstract class BaseBusinessHandler extends OidbWrapper {
return null;
}
public void addBusinessObserver(ToServiceMsg toServiceMsg, BusinessObserver businessObserver, boolean z) {
}
public final <T> 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<String> getCommandList();
protected abstract Set<String> getPushCommandList();

View File

@ -8,6 +8,8 @@ public abstract class BusinessHandler extends BaseBusinessHandler {
public BusinessHandler(AppInterface appInterface) {
}
protected abstract Class<? extends BusinessObserver> observerClass();
@Override
public Set<String> getCommandList() {
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) {
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 {
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()}")

View File

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

View File

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

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
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("同步配置中...")
}
}

View File

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

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