-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
461 additions
and
272 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package moe.fuqiuluo.entries | ||
|
||
import kotlinx.serialization.Serializable | ||
import kotlinx.serialization.protobuf.ProtoNumber | ||
|
||
|
||
@Serializable | ||
data class TextMsgExtPbResvAttr( | ||
@ProtoNumber(1) val wording: ByteArray?, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package moe.fuqiuluo.entries | ||
|
||
import kotlinx.serialization.Serializable | ||
import kotlinx.serialization.protobuf.ProtoNumber | ||
|
||
/* | ||
message SsoSecureInfo { | ||
required bytes sec_sig = 1; | ||
required bytes device_token = 2; | ||
required bytes extra = 3; | ||
} | ||
*/ | ||
@Serializable | ||
data class QQSsoSecureInfo( | ||
@ProtoNumber(1) val secSig: ByteArray, | ||
@ProtoNumber(2) val deviceToken: ByteArray, | ||
@ProtoNumber(3) val extra: ByteArray | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package moe.qwq.miko.ext | ||
|
||
import com.google.protobuf.UnknownFieldSet | ||
|
||
|
||
fun UnknownFieldSet.getUnknownObject(number: Int): UnknownFieldSet { | ||
return getField(number).groupList.firstOrNull() ?: getField(number).lengthDelimitedList.firstOrNull()?.let { | ||
UnknownFieldSet.parseFrom(it) | ||
} ?: throw RuntimeException("failed to fetch object") | ||
} | ||
|
||
fun UnknownFieldSet.getUnknownObjects(number: Int): List<UnknownFieldSet> { | ||
return getField(number).groupList.ifEmpty { | ||
getField(number).lengthDelimitedList.map { | ||
UnknownFieldSet.parseFrom(it) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,184 @@ | ||
package moe.qwq.miko.hooks | ||
|
||
import android.content.Context | ||
import com.google.protobuf.ByteString | ||
import com.google.protobuf.UnknownFieldSet | ||
import com.tencent.mobileqq.fe.FEKit | ||
import com.tencent.mobileqq.msf.core.MsfCore | ||
import com.tencent.mobileqq.sign.QQSecuritySign | ||
import com.tencent.qphone.base.remote.ToServiceMsg | ||
import de.robv.android.xposed.XC_MethodHook | ||
import de.robv.android.xposed.XposedBridge | ||
import kotlinx.io.core.BytePacketBuilder | ||
import kotlinx.io.core.readBytes | ||
import kotlinx.io.core.toByteArray | ||
import kotlinx.io.core.writeFully | ||
import kotlinx.serialization.encodeToByteArray | ||
import kotlinx.serialization.protobuf.ProtoBuf | ||
import moe.fuqiuluo.entries.QQSsoSecureInfo | ||
import moe.fuqiuluo.entries.TextMsgExtPbResvAttr | ||
import moe.fuqiuluo.processor.HookAction | ||
import moe.qwq.miko.actions.ActionProcess | ||
import moe.qwq.miko.actions.HookCodec | ||
import moe.qwq.miko.actions.IAction | ||
import moe.qwq.miko.actions.PatchMsfCore | ||
import moe.qwq.miko.ext.EMPTY_BYTE_ARRAY | ||
import moe.qwq.miko.ext.getUnknownObject | ||
import moe.qwq.miko.ext.getUnknownObjects | ||
import moe.qwq.miko.ext.toHexString | ||
import moe.qwq.miko.ext.toInnerValuesString | ||
import moe.qwq.miko.internals.QQInterfaces | ||
import moe.qwq.miko.internals.hijackers.IHijacker | ||
import moe.qwq.miko.internals.setting.QwQSetting | ||
import moe.qwq.miko.utils.AesUtils.aesEncrypt | ||
import moe.qwq.miko.utils.AesUtils.md5 | ||
import moe.qwq.miko.utils.PlatformTools | ||
import tencent.im.msg.im_msg_body.MsgBody | ||
import java.security.MessageDigest | ||
import javax.crypto.Cipher | ||
import javax.crypto.spec.IvParameterSpec | ||
import javax.crypto.spec.SecretKeySpec | ||
|
||
@HookAction(desc = "消息加密抄送") | ||
class MessageEncrypt: IAction { | ||
class MessageEncrypt: IAction, QQInterfaces() { | ||
override fun onRun(ctx: Context) { | ||
HookCodec.hijackers.add(object: IHijacker { | ||
override fun onHandle( | ||
param: XC_MethodHook.MethodHookParam, | ||
uin: String, | ||
cmd: String, | ||
seq: Int, | ||
buffer: ByteArray, | ||
bufferIndex: Int | ||
): Boolean { | ||
this@MessageEncrypt.onHandle(param, uin, cmd, seq, buffer, bufferIndex) | ||
return false | ||
} | ||
override val command: String = "MessageSvc.PbSendMsg" | ||
}) | ||
} | ||
|
||
private fun onHandle(param: XC_MethodHook.MethodHookParam, uin: String, cmd: String, seq: Int, buffer: ByteArray, bufferIndex: Int) { | ||
if (buffer.size <= 4) return | ||
val unknownFields = UnknownFieldSet.parseFrom(buffer.copyOfRange(4, buffer.size)) | ||
if (!unknownFields.hasField(1)) return | ||
val routingHead = unknownFields.getUnknownObject(1) | ||
if (routingHead.hasField(1)) return // 私聊消息不加密 | ||
val msgBody = unknownFields.getUnknownObject(3) | ||
|
||
val builder = UnknownFieldSet.newBuilder(unknownFields) | ||
builder.clearField(3) // 清除原消息体 | ||
|
||
val newMsgBody = generateEncryptedMsgBody(msgBody) | ||
builder.addField(3, UnknownFieldSet.Field.newBuilder().also { | ||
it.addLengthDelimited(newMsgBody.toByteString()) | ||
}.build()) | ||
|
||
val data = builder.build().toByteArray() | ||
|
||
if (bufferIndex == 15 && param.args[13] != null) { | ||
//PlatformTools.copyToClipboard(text = "Sign13: ${(param.args[13] as ByteArray).toHexString()}") | ||
// 因为包体改变,重新签名 | ||
val qqSecurityHead = UnknownFieldSet.parseFrom(param.args[13] as ByteArray) | ||
val qqSecurityHeadBuilder = UnknownFieldSet.newBuilder(qqSecurityHead) | ||
//XposedBridge.log(qqSecurityHead.getField(24).toInnerValuesString()) | ||
qqSecurityHeadBuilder.clearField(24) | ||
val sign = FEKit.getInstance().getSign(cmd, data, seq, uin) | ||
//XposedBridge.log(sign.toInnerValuesString()) | ||
qqSecurityHeadBuilder.addField(24, UnknownFieldSet.Field.newBuilder().also { | ||
it.addLengthDelimited(ByteString.copyFrom(ProtoBuf.encodeToByteArray(QQSsoSecureInfo( | ||
secSig = sign.sign, | ||
extra = sign.extra, | ||
deviceToken = sign.token | ||
)))) | ||
}.build()) | ||
param.args[13] = qqSecurityHeadBuilder.build().toByteArray() | ||
} | ||
|
||
if (bufferIndex == 15 && param.args[14] != null) { | ||
//PlatformTools.copyToClipboard(text = "Sign14: ${(param.args[14] as ByteArray).toHexString()}") | ||
val qqSecurityHead = UnknownFieldSet.parseFrom(param.args[14] as ByteArray) | ||
val qqSecurityHeadBuilder = UnknownFieldSet.newBuilder(qqSecurityHead) | ||
qqSecurityHeadBuilder.clearField(24) | ||
val sign = FEKit.getInstance().getSign(cmd, data, seq, uin) | ||
qqSecurityHeadBuilder.addField(24, UnknownFieldSet.Field.newBuilder().also { | ||
it.addLengthDelimited(ByteString.copyFrom(ProtoBuf.encodeToByteArray(QQSsoSecureInfo( | ||
secSig = sign.sign, | ||
extra = sign.extra, | ||
deviceToken = sign.token | ||
)))) | ||
}.build()) | ||
param.args[14] = qqSecurityHeadBuilder.build().toByteArray() | ||
} | ||
|
||
//PlatformTools.copyToClipboard(text = "SendData: ${(data).toHexString()}") | ||
param.args[bufferIndex] = BytePacketBuilder().also { | ||
it.writeInt(data.size + 4) | ||
it.writeFully(data) | ||
}.build().readBytes() | ||
//sendBuffer(cmd, true, data) | ||
//param.result = EMPTY_BYTE_ARRAY | ||
} | ||
|
||
private fun generateEncryptedMsgBody(msgBody: UnknownFieldSet): UnknownFieldSet { | ||
val encryptKey = QwQSetting.getSetting<String>(name).getValue(null, null) | ||
if (encryptKey.isBlank()) { | ||
// 未设置加密密钥 | ||
return msgBody | ||
} | ||
|
||
val elements = UnknownFieldSet.Field.newBuilder() | ||
msgBody.getUnknownObject(1).let { richText -> | ||
richText.getUnknownObjects(2).forEach { element -> | ||
if (element.hasField(37) || element.hasField(9)) { | ||
elements.addLengthDelimited(element.toByteString()) // 通用字段,不自己合成 | ||
} | ||
} | ||
} | ||
|
||
val newMsgBody = UnknownFieldSet.newBuilder() | ||
val richText = UnknownFieldSet.newBuilder() | ||
|
||
/* elements.addLengthDelimited(UnknownFieldSet.newBuilder().also { builder -> | ||
builder.addField(1, UnknownFieldSet.Field.newBuilder().also { | ||
it.addLengthDelimited(UnknownFieldSet.newBuilder().also { textElement -> | ||
textElement.addField(1, UnknownFieldSet.Field.newBuilder().also { content -> | ||
content.addLengthDelimited(ByteString.copyFromUtf8("[爱你]")) | ||
}.build()) | ||
textElement.addField(12, UnknownFieldSet.Field.newBuilder().also { content -> | ||
content.addLengthDelimited(ByteString.copyFrom(ProtoBuf.encodeToByteArray(TextMsgExtPbResvAttr( | ||
wording = BytePacketBuilder().also { | ||
it.writeInt(0x114514) | ||
it.writeInt(encryptKey.hashCode()) | ||
it.writeFully(aesEncrypt(msgBody.toByteArray(), md5(encryptKey))) | ||
}.build().readBytes() | ||
)))) | ||
}.build()) | ||
}.build().toByteString()) | ||
}.build()) | ||
}.build().toByteString()) // add text*/ | ||
|
||
elements.addLengthDelimited(DEFAULT_FACE) // add image | ||
|
||
richText.addField(2, elements.build()) | ||
|
||
newMsgBody.addField(1, UnknownFieldSet.Field.newBuilder().also { | ||
it.addLengthDelimited(richText.build().toByteString()) | ||
}.build()) | ||
|
||
return newMsgBody.build() | ||
} | ||
|
||
override fun canRun(): Boolean { | ||
val setting = QwQSetting.getSetting<String>(name) | ||
return setting.getValue(null, null).isNotBlank() | ||
} | ||
|
||
//override val process: ActionProcess = ActionProcess.MAIN | ||
override val process: ActionProcess = ActionProcess.MSF | ||
|
||
override val name: String = QwQSetting.MESSAGE_ENCRYPT | ||
} | ||
|
||
private val DEFAULT_FACE by lazy { | ||
ByteString.fromHex("323d0a055b5177515d1002180122101a156dd3d4367c701aecfe157b16f7f728b5bf0e30033a1035636664613661666530633537383466480050c80158c801") | ||
} |
Oops, something went wrong.