Extract login subcommands to simplify WtLogin, add exchange_emp, #833

This commit is contained in:
Him188 2021-01-17 18:31:22 +08:00
parent 3098a7d953
commit bb348930d8
11 changed files with 548 additions and 370 deletions

View File

@ -121,7 +121,7 @@ kotlin {
jvmTest {
dependencies {
implementation("org.pcap4j:pcap4j-distribution:1.8.2")
implementation("net.mamoe:mirai-login-solver-selenium:1.0-dev-14")
// implementation("net.mamoe:mirai-login-solver-selenium:1.0-dev-14")
}
}
}

View File

@ -41,6 +41,9 @@ import net.mamoe.mirai.internal.network.protocol.packet.login.ConfigPushSvc
import net.mamoe.mirai.internal.network.protocol.packet.login.Heartbeat
import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLogin2
import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLogin20
import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLogin9
import net.mamoe.mirai.internal.utils.*
import net.mamoe.mirai.network.*
import net.mamoe.mirai.utils.*
@ -175,12 +178,12 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
fun loginSolverNotNull() = bot.configuration.loginSolver.notnull()
var response: WtLogin.Login.LoginPacketResponse =
WtLogin.Login.SubCommand9(bot.client, allowSlider).sendAndExpect()
WtLogin9(bot.client, allowSlider).sendAndExpect()
mainloop@ while (true) {
when (response) {
is WtLogin.Login.LoginPacketResponse.UnsafeLogin -> {
loginSolverNotNull().onSolveUnsafeDeviceLoginVerify(bot, response.url)
response = WtLogin.Login.SubCommand9(bot.client, allowSlider).sendAndExpect()
response = WtLogin9(bot.client, allowSlider).sendAndExpect()
}
is WtLogin.Login.LoginPacketResponse.Captcha -> when (response) {
@ -190,7 +193,7 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
//refresh captcha
result = "ABCD"
}
response = WtLogin.Login.SubCommand2.SubmitPictureCaptcha(bot.client, response.sign, result)
response = WtLogin2.SubmitPictureCaptcha(bot.client, response.sign, result)
.sendAndExpect()
continue@mainloop
}
@ -224,7 +227,7 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
}
throw error
}
response = WtLogin.Login.SubCommand2.SubmitSliderCaptcha(bot.client, ticket).sendAndExpect()
response = WtLogin2.SubmitSliderCaptcha(bot.client, ticket).sendAndExpect()
continue@mainloop
}
}
@ -243,7 +246,7 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
}
is WtLogin.Login.LoginPacketResponse.DeviceLockLogin -> {
response = WtLogin.Login.SubCommand20(
response = WtLogin20(
bot.client,
response.t402
).sendAndExpect()

View File

@ -307,6 +307,11 @@ internal open class QQAndroidClient(
var reserveUinInfo: ReserveUinInfo? = null
lateinit var wLoginSigInfo: WLoginSigInfo
var tlv113: ByteArray? = null
/**
* from tlvMap119
*/
var tlv16a: ByteArray? = null
lateinit var qrPushSig: ByteArray
lateinit var mainDisplayName: ByteArray
@ -373,6 +378,9 @@ internal class LoginExtraData(
internal class WLoginSigInfo(
val uin: Long,
val encryptA1: ByteArray?, // sigInfo[0]
/**
* WARNING, please check [QQAndroidClient.tlv16a]
*/
val noPicSig: ByteArray?, // sigInfo[1]
val G: ByteArray, // sigInfo[2]
val dpwd: ByteArray,

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
@ -122,6 +122,7 @@ internal val PacketLogger: MiraiLoggerWithSwitch by lazy {
internal object KnownPacketFactories {
object OutgoingFactories : List<OutgoingPacketFactory<*>> by mutableListOf(
WtLogin.Login,
WtLogin.ExchangeEmp,
StatSvc.Register,
StatSvc.GetOnlineStatus,
StatSvc.GetDevLoginInfo,

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
@ -15,10 +15,16 @@ import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.toByteArray
import kotlinx.io.core.writeFully
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.guid
import net.mamoe.mirai.internal.network.protocol.LoginType
import net.mamoe.mirai.internal.utils.GuidSource
import net.mamoe.mirai.internal.utils.MacOrAndroidIdChangeFlag
import net.mamoe.mirai.internal.utils.NetworkType
import net.mamoe.mirai.internal.utils.guidFlag
import net.mamoe.mirai.internal.utils.io.*
import net.mamoe.mirai.utils.currentTimeMillis
import net.mamoe.mirai.utils.generateDeviceInfoData
import net.mamoe.mirai.utils.md5
import net.mamoe.mirai.utils.toByteArray
import kotlin.random.Random
@ -79,6 +85,26 @@ internal fun BytePacketBuilder.t18(
} shouldEqualsTo 22
}
internal fun BytePacketBuilder.t106(
appId: Long = 16L,
client: QQAndroidClient
) {
return t106(
appId,
client.subAppId /* maybe 1*/,
client.appClientVersion,
client.uin,
true,
client.account.passwordMd5,
0,
client.uin.toByteArray(),
client.tgtgtKey,
true,
client.device.guid,
LoginType.PASSWORD,
client.ssoVersion
)
}
internal fun BytePacketBuilder.t106(
appId: Long = 16L,
@ -291,6 +317,29 @@ internal fun BytePacketBuilder.t112(
}
}
internal fun BytePacketBuilder.t144(
client: QQAndroidClient
) {
return t144(
androidId = client.device.androidId,
androidDevInfo = client.device.generateDeviceInfoData(),
osType = client.device.osType,
osVersion = client.device.version.release,
networkType = client.networkType,
simInfo = client.device.simInfo,
unknown = byteArrayOf(),
apn = client.device.apn,
isGuidFromFileNull = false,
isGuidAvailable = true,
isGuidChanged = false,
guidFlag = guidFlag(GuidSource.FROM_STORAGE, MacOrAndroidIdChangeFlag(0)),
buildModel = client.device.model,
guid = client.device.guid,
buildBrand = client.device.brand,
tgtgtKey = client.tgtgtKey
)
}
internal fun BytePacketBuilder.t144(
// t109
androidId: ByteArray,
@ -499,7 +548,22 @@ internal fun BytePacketBuilder.t141(
}
internal fun BytePacketBuilder.t511(
domains: List<String>
domains: List<String> = listOf(
"tenpay.com",
"openmobile.qq.com",
"docs.qq.com",
"connect.qq.com",
"qzone.qq.com",
"vip.qq.com",
"gamecenter.qq.com",
"qun.qq.com",
"game.qq.com",
"qqweb.qq.com",
"office.qq.com",
"ti.qq.com",
"mail.qq.com",
"mma.qq.com",
)
) {
writeShort(0x511)
writeShortLVPacket {

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,54 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.guid
import net.mamoe.mirai.internal.network.protocol.packet.*
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
internal object WtLogin15 : WtLoginExt {
private const val subCommand = 15.toShort()
private const val appId = 16L
operator fun invoke(
client: QQAndroidClient,
) = WtLogin.ExchangeEmp.buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
writeSsoPacket(client, client.subAppId, WtLogin.ExchangeEmp.commandName, sequenceId = sequenceId) {
writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) {
writeShort(subCommand) // subCommand
writeShort(21)
t18(16, uin = client.uin)
t1(client.uin, client.device.ipAddress)
t106(appId, client)
t116(client.miscBitMap, client.subSigMap)
t100(appId, client.subAppId, client.appClientVersion, client.ssoVersion, client.mainSigMap)
t107(0)
t142(client.apkId)
t144(client)
t145(client.device.guid)
t16a(client.tlv16a ?: byteArrayOf()) // new
t154(sequenceId)
t141(client.device.simInfo, client.networkType, client.device.apn)
t8(2052)
t511()
t147(appId, client.apkVersionName, client.apkSignatureMd5)
t177(buildTime = client.buildTime, buildVersion = client.sdkVersion)
t187(client.device.macAddress)
t188(client.device.androidId)
t194(client.device.imsiMd5)
t202(client.device.wifiBSSID, client.device.wifiSSID)
t516()
}
}
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.protocol.packet.*
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
internal object WtLogin2 : WtLoginExt {
fun SubmitSliderCaptcha(
client: QQAndroidClient,
ticket: String
): OutgoingPacket = WtLogin.Login.buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
writeSsoPacket(client, client.subAppId, WtLogin.Login.commandName, sequenceId = sequenceId) {
writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) {
writeShort(2) // subCommand
writeShort(4) // count of TLVs
t193(ticket)
t8(2052)
t104(client.t104)
t116(client.miscBitMap, client.subSigMap)
}
}
}
fun SubmitPictureCaptcha(
client: QQAndroidClient,
captchaSign: ByteArray,
captchaAnswer: String
): OutgoingPacket = WtLogin.Login.buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
writeSsoPacket(client, client.subAppId, WtLogin.Login.commandName, sequenceId = sequenceId) {
writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) {
writeShort(2) // subCommand
writeShort(4) // count of TLVs
t2(captchaAnswer, captchaSign, 0)
t8(2052)
t104(client.t104)
t116(client.miscBitMap, client.subSigMap)
}
}
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin
import kotlinx.io.core.toByteArray
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.guid
import net.mamoe.mirai.internal.network.protocol.packet.*
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
import net.mamoe.mirai.utils.md5
internal object WtLogin20 : WtLoginExt {
operator fun invoke(
client: QQAndroidClient,
t402: ByteArray
): OutgoingPacket = WtLogin.Login.buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
writeSsoPacket(client, client.subAppId, WtLogin.Login.commandName, sequenceId = sequenceId) {
writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) {
writeShort(20) // subCommand
writeShort(4) // count of TLVs, probably ignored by server?
t8(2052)
t104(client.t104)
t116(client.miscBitMap, client.subSigMap)
t401((client.device.guid + "stMNokHgxZUGhsYp".toByteArray() + t402).md5())
}
}
}
}

View File

@ -0,0 +1,114 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin
import kotlinx.io.core.buildPacket
import kotlinx.io.core.readBytes
import kotlinx.io.core.toByteArray
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.guid
import net.mamoe.mirai.internal.network.protocol.packet.*
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
internal object WtLogin9 : WtLoginExt {
private const val appId = 16L
operator fun invoke(
client: QQAndroidClient,
allowSlider: Boolean
): OutgoingPacket = WtLogin.Login.buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
writeSsoPacket(client, client.subAppId, WtLogin.Login.commandName, sequenceId = sequenceId) {
writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) {
writeShort(9) // subCommand
writeShort(if (allowSlider) 0x18 else 0x17) // count of TLVs, probably ignored by server?
//writeShort(LoginType.PASSWORD.value.toShort())
t18(appId, client.appClientVersion, client.uin)
t1(client.uin, client.device.ipAddress)
t106(appId, client)
/* // from GetStWithPasswd
int mMiscBitmap = this.mMiscBitmap;
if (t.uinDeviceToken) {
mMiscBitmap = (this.mMiscBitmap | 0x2000000);
}
// defaults true
if (ConfigManager.get_loginWithPicSt()) appIdList = longArrayOf(1600000226L)
*/
t116(client.miscBitMap, client.subSigMap)
t100(appId, client.subAppId, client.appClientVersion, client.ssoVersion, client.mainSigMap)
t107(0)
t108(client.device.imei.toByteArray())
// t108(byteArrayOf())
// ignored: t104()
t142(client.apkId)
// if login with non-number uin
// t112()
t144(client)
//this.build().debugPrint("傻逼")
t145(client.device.guid)
t147(appId, client.apkVersionName, client.apkSignatureMd5)
/*
if (client.miscBitMap and 0x80 != 0) {
t166(1)
}
*/
// ignored t16a because array5 is null
t154(sequenceId)
t141(client.device.simInfo, client.networkType, client.device.apn)
t8(2052)
t511()
// ignored t172 because rollbackSig is null
// ignored t185 because loginType is not SMS
// ignored t400 because of first login
t187(client.device.macAddress)
t188(client.device.androidId)
t194(client.device.imsiMd5)
if (allowSlider) {
t191()
}
/*
t201(N = byteArrayOf())*/
t202(client.device.wifiBSSID, client.device.wifiSSID)
t177(
buildTime = client.buildTime,
buildVersion = client.sdkVersion,
)
t516()
t521()
t525(buildPacket {
t536(buildPacket {
//com.tencent.loginsecsdk.ProtocolDet#packExtraData
writeByte(1) // const
writeByte(0) // data count
}.readBytes())
})
// this.build().debugPrint("傻逼")
// ignored t318 because not logging in by QR
}
}
}
}

View File

@ -0,0 +1,184 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin
import kotlinx.io.core.*
import net.mamoe.mirai.internal.network.LoginExtraData
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.guid
import net.mamoe.mirai.internal.network.protocol.packet.Tlv
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
import net.mamoe.mirai.internal.network.protocol.packet.t145
import net.mamoe.mirai.internal.network.readUShortLVByteArray
import net.mamoe.mirai.internal.utils.io.writeShortLVByteArray
import net.mamoe.mirai.utils.*
internal inline fun WtLoginExt.analysisTlv0x531(
t531: ByteArray,
handler: (a1: ByteArray, noPicSig: ByteArray) -> Unit
) {
val map = t531.toReadPacket().withUse { _readTLVMap() }
val t106 = map[0x106]
val t16a = map[0x16a]
val t113 = map[0x113]
val t10c = map[0x10c]
if (t106 != null && t16a != null && t113 != null && t10c != null) {
handler(t106 + t10c, t16a)
}
}
internal interface WtLoginExt { // so as not to register to global extension
fun onErrorMessage(tlvMap: TlvMap): WtLogin.Login.LoginPacketResponse.Error? {
return tlvMap[0x149]?.read {
discardExact(2) //type
val title: String = readUShortLVString()
val content: String = readUShortLVString()
val otherInfo: String = readUShortLVString()
WtLogin.Login.LoginPacketResponse.Error(title, content, otherInfo)
} ?: tlvMap[0x146]?.read {
discardExact(2) // ver
discardExact(2) // code
val title = readUShortLVString()
val message = readUShortLVString()
val errorInfo = readUShortLVString()
WtLogin.Login.LoginPacketResponse.Error(title, message, errorInfo)
}
}
fun TlvMap.getOrEmpty(key: Int): ByteArray {
return this[key] ?: byteArrayOf()
}
/**
* @throws error
*/
fun QQAndroidClient.parseWFastLoginInfoDataOutA1(t169: ByteArray): ByteReadPacket {
val map = t169.toReadPacket().withUse { _readTLVMap() }
val t106 = map[0x106]
val t10c = map[0x10c]
val t16a = map[0x16a]
check(t106 != null) { "getWFastLoginInfoDataOutA1: Cannot find tlv 0x106!!" }
check(t10c != null) { "getWFastLoginInfoDataOutA1: Cannot find tlv 0x10c!!" }
check(t16a != null) { "getWFastLoginInfoDataOutA1: Cannot find tlv 0x16a!!" }
return buildPacket {
writeByte(64)
writeShort(4)
// TLV
writeShort(0x106)
writeShortLVByteArray(t106)
writeShort(0x10c)
writeShortLVByteArray(t10c)
writeShort(0x16a)
writeShortLVByteArray(t16a)
t145(device.guid)
}
}
/**
* login extra data
*/
fun QQAndroidClient.analysisTlv537(t537: ByteArray) = t537.read {
//discardExact(2)
loginExtraData = LoginExtraData( // args are to correct order
uin = readUInt().toLong(),
ip = readBytes(readByte().toInt() and 0xff),
time = readInt(), // correct
version = readInt()
)
}
/**
* pwd flag
*/
fun QQAndroidClient.analysisTlv186(t186: ByteArray) = t186.read {
discardExact(1)
pwdFlag = readByte().toInt() == 1
}
/**
* 设置 [QQAndroidClient.uin]
*/
fun QQAndroidClient.analysisTlv113(t113: ByteArray) = t113.read {
_uin = readUInt().toLong()
/*
// nothing to do
if (!asyncContext.ifQQLoginInQim(class_1048.productType)) {
this.field_61436.method_62330(this.field_61436.field_63973, this.field_61436.uin);
}
*/
}
/**
* 设置 [QQAndroidClient.timeDifference] [QQAndroidClient.ipFromT149]
*/
fun QQAndroidClient.analysisTlv130(t130: ByteArray) = t130.read {
discardExact(2)
timeDifference = readUInt().toLong() - currentTimeSeconds()
ipFromT149 = readBytes(4)
}
fun QQAndroidClient.analysisTlv150(t150: ByteArray) {
this.t150 = Tlv(t150)
}
fun QQAndroidClient.analysisTlv161(t161: ByteArray) {
val tlv = t161.toReadPacket().apply { discardExact(2) }.withUse { _readTLVMap() }
tlv[0x173]?.let { analysisTlv173(it) }
tlv[0x17f]?.let { analysisTlv17f(it) }
tlv[0x172]?.let { rollbackSig = it }
}
/**
* server host
*/
fun QQAndroidClient.analysisTlv173(t173: ByteArray) {
t173.read {
val type = readByte()
val host = readUShortLVString()
val port = readShort()
bot.logger.warning("服务器: host=$host, port=$port, type=$type")
// SEE oicq_request.java at method analysisT173
}
}
/**
* ipv6 address
*/
fun QQAndroidClient.analysisTlv17f(t17f: ByteArray) {
t17f.read {
val type = readByte()
val host = readUShortLVString()
val port = readShort()
bot.logger.warning("服务器 ipv6: host=$host, port=$port, type=$type")
// SEE oicq_request.java at method analysisT17f
}
}
fun Input.readUShortLVString(): String = String(this.readUShortLVByteArray())
}