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

其中kernelServicecom.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