LSPosed - 使用协议层禁止QQ撤回事件
看了一下QA源代码,用的是native hook去实现的反撤回,我觉得呢,太麻烦!
在协议层把撤回包拦截就好了!
根据我多年分析协议的经验,QQ的撤回主要来源于两个包:
trpc.msg.register_proxy.RegisterProxy.InfoSyncPush
trpc.msg.olpush.OlPushService.MsgPush
第一个管的是消息的同步,也就是历史撤回事件的同步(还有的别的消息事件戳一戳什么的),而第二个比较单一,一般只携带有一个事件,例如戳一戳或者撤回事件。
Hook NTNative收包
kernelService.wrapperSession.javaClass.hookMethod("onMsfPush").before {
runCatching {
val cmd = it.args[0] as String
val buffer = it.args[1] as ByteArray
if (cmd == "trpc.msg.register_proxy.RegisterProxy.InfoSyncPush") {
// 协议反撤回
} else if (cmd == "trpc.msg.olpush.OlPushService.MsgPush") {
// 反撤回逻辑
}
}.onFailure {
XposedBridge.log(it)
}
}
其中kernelService
是com.tencent.qqnt.kernel.api.IKernelService
,那么问题来了,解析全在native,我们不知道protobuf的结构呀!别急,谷歌的protobuf组件库贴心的为我们实现了一个叫UnknownFieldSet
的东西,使用这个东西,我们可以轻松解析不认识的网络包。
我们可以参考:https://github.com/fuqiuluo/fastprotobuf,草gitcode偷偷克隆我的垃圾项目,https://gitcode.com/fuqiuluo/fastprotobuf。
那我们先将代码改成这样:
private fun initNTKernel(msgService: MsgService) {
XposedBridge.log("[QwQ] Init NT Kernel.")
kernelService.wrapperSession.javaClass.hookMethod("onMsfPush").before {
runCatching {
val cmd = it.args[0] as String
val buffer = it.args[1] as? ByteArray ?: return@before
when (cmd) {
"trpc.msg.register_proxy.RegisterProxy.InfoSyncPush" -> {
val unknownFieldSet = UnknownFieldSet.parseFrom(buffer)
AioListener.onInfoSyncPush(unknownFieldSet).onSuccess { new ->
it.args[1] = new.toByteArray()
}.onFailure {
XposedBridge.log(it)
}
}
"trpc.msg.olpush.OlPushService.MsgPush" -> {
val msgPush = ProtoBuf.decodeFromByteArray<MessagePush>(buffer)
if(AioListener.onMsgPush(msgPush)) {
it.result = Unit // 提前结束
} else {
return@before
}
}
else -> { }
}
}.onFailure {
XposedBridge.log(it)
}
}
msgService.addMsgListener(AioListener)
}
除了同步消息包
fun onInfoSyncPush(fieldSet: UnknownFieldSet): Result<UnknownFieldSet> {
val type = fieldSet.getField(3)
if (!type.varintList.any { it == 2L }) {
return Result.success(fieldSet)
}
val builder = UnknownFieldSet.newBuilder(fieldSet)
builder.clearField(8) // 移除content的内容
val contentsBuilder = UnknownFieldSet.Field.newBuilder()
val contents = fieldSet.getField(8)
contents.groupList.forEach { content ->
var isRecallEvent = false
val bodies = content.getField(4)
bodies.groupList.forEach { body ->
val msgs = body.getField(8)
msgs.groupList.forEach { msg ->
val msgHead = msg.getField(2).groupList.first()
val msgType = msgHead.getField(1).varintList.first()
val msgSubType = msgHead.getField(2).varintList.first()
isRecallEvent = (msgType == 528L && msgSubType == 138L) || (msgType == 732L && msgSubType == 17L)
}
}
if (!isRecallEvent) {
contentsBuilder.addGroup(content)
}
}
builder.addField(8, contentsBuilder.build())
return Result.success(builder.build())
}
基础事件推送
fun onMsgPush(msgPush: MessagePush): Boolean {
val msgType = msgPush.msgBody.content.msgType
val subType = msgPush.msgBody.content.msgSubType
return onMessage(msgType, subType, msgPush.msgBody)
}
private fun onMessage(msgType: Int, subType: Int, msgBody: Message): Boolean {
return when(msgType) {
528 -> when (subType) {
138 -> onC2CRecall(msgBody.msgHead, msgBody.body!!.richMsg)
else -> false
}
732 -> when (subType) {
17 -> onGroupRecall(msgBody, msgBody.body!!.richMsg) // 自己处理逻辑
else -> false
}
else -> false
}
}
详细代码在QwQ项目:https://github.com/KarinJS/QwQ