Update login protocol (#2433)

* Update login protocol
Still need testing

* Turn off debug option and make t547 null when failed

* Fix wrong convert method and improve tips

* Remove unused part and improve tips

* Fix typo

* Inline resultStatus for performance

* Rename pow to PoW, the name should be "Proof of Work"

* Add shadow and deps-test for kt-bignum

* Try to fix deps-test

* Fix deps-test again
This commit is contained in:
sandtechnology 2023-01-18 17:41:21 +08:00 committed by GitHub
parent 56dea84336
commit cc7f35519e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 273 additions and 5 deletions

View File

@ -267,6 +267,9 @@ const val `netty-transport` = "io.netty:netty-transport:${Versions.netty}"
const val `netty-buffer` = "io.netty:netty-buffer:${Versions.netty}"
const val `bouncycastle` = "org.bouncycastle:bcprov-jdk15on:${Versions.bouncycastle}"
const val `kt-bignum` = "com.ionspin.kotlin:bignum:0.3.7"
val `kt-bignum_relocated` = RelocatedDependency(`kt-bignum`, "com.ionspin.kotlin.bignum")
const val `maven-resolver-api` = "org.apache.maven.resolver:maven-resolver-api:${Versions.mavenArtifactResolver}"
const val `maven-resolver-impl` = "org.apache.maven.resolver:maven-resolver-impl:${Versions.mavenArtifactResolver}"
const val `maven-resolver-connector-basic` =

View File

@ -25,6 +25,7 @@ dependencies {
api(project(":mirai-core-utils"))
implementation(`slf4j-api`) // Required by mirai-console
relocateImplementation(project, `kt-bignum_relocated`)
relocateImplementation(project, `ktor-client-core_relocated`)
relocateImplementation(project, `ktor-client-okhttp_relocated`)
relocateImplementation(project, `ktor-io_relocated`)

View File

@ -24,6 +24,9 @@ public fun String.sha1(): ByteArray = toByteArray().sha1()
public expect fun ByteArray.sha1(offset: Int = 0, length: Int = size - offset): ByteArray
public fun String.sha256(): ByteArray = toByteArray().sha256()
public expect fun ByteArray.sha256(offset: Int = 0, length: Int = size - offset): ByteArray
///////////////////////////////////////////////////////////////////////////
// How to choose 'inflate', 'inflateAllAvailable', 'InflateInput'?
@ -112,4 +115,4 @@ public expect fun DeflateInput(source: Input): Input
/**
* @see DeflateInput
*/
public fun Input.deflateInput(): Input = DeflateInput(this)
public fun Input.deflateInput(): Input = DeflateInput(this)

View File

@ -13,7 +13,7 @@
package net.mamoe.mirai.utils
import io.ktor.utils.io.core.*
import io.ktor.utils.io.streams.asInput
import io.ktor.utils.io.streams.*
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
@ -50,6 +50,10 @@ public fun InputStream.sha1(): ByteArray {
return digest("SHA-1")
}
public fun InputStream.sha256(): ByteArray {
return digest("SHA-256")
}
public actual fun ByteArray.md5(offset: Int, length: Int): ByteArray {
checkOffsetAndLength(offset, length)
return MessageDigest.getInstance("MD5").apply { update(this@md5, offset, length) }.digest()
@ -62,6 +66,12 @@ public actual fun ByteArray.sha1(offset: Int, length: Int): ByteArray {
return MessageDigest.getInstance("SHA-1").apply { update(this@sha1, offset, length) }.digest()
}
@JvmOverloads
public actual fun ByteArray.sha256(offset: Int, length: Int): ByteArray {
checkOffsetAndLength(offset, length)
return MessageDigest.getInstance("SHA-256").apply { update(this@sha256, offset, length) }.digest()
}
@JvmOverloads
public actual fun ByteArray.gzip(offset: Int, length: Int): ByteArray {
ByteArrayOutputStream().use { buf ->

View File

@ -31,6 +31,11 @@ public actual fun ByteArray.sha1(offset: Int, length: Int): ByteArray = SHA1.cre
return digest().bytes
}
public actual fun ByteArray.sha256(offset: Int, length: Int): ByteArray = SHA256.create().run {
update(this@sha256, offset, length)
return digest().bytes
}
/**
* WARNING: DO NOT SET THIS BUFFER TOO SMALL, OR YOU WILL SEE COMPRESSION ERROR.
*/

View File

@ -272,3 +272,79 @@ internal class SHA1 : SHA(chunkSize = 64, digestSize = 20) {
for (n in out.indices) out[n] = (h[n / 4] ushr (24 - 8 * (n % 4))).toByte()
}
}
internal class SHA256 : SHA(chunkSize = 64, digestSize = 32) {
companion object : HasherFactory({ SHA256() }) {
private val H = intArrayOf(
0x6a09e667, -0x4498517b, 0x3c6ef372, -0x5ab00ac6,
0x510e527f, -0x64fa9774, 0x1f83d9ab, 0x5be0cd19
)
private val K = intArrayOf(
0x428a2f98, 0x71374491, -0x4a3f0431, -0x164a245b,
0x3956c25b, 0x59f111f1, -0x6dc07d5c, -0x54e3a12b,
-0x27f85568, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, -0x7f214e02, -0x6423f959, -0x3e640e8c,
-0x1b64963f, -0x1041b87a, 0x0fc19dc6, 0x240ca1cc,
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
-0x67c1aeae, -0x57ce3993, -0x4ffcd838, -0x40a68039,
-0x391ff40d, -0x2a586eb9, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, -0x7e3d36d2, -0x6d8dd37b,
-0x5d40175f, -0x57e599b5, -0x3db47490, -0x3893ae5d,
-0x2e6d17e7, -0x2966f9dc, -0xbf1ca7b, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, -0x7b3787ec, -0x7338fdf8,
-0x6f410006, -0x5baf9315, -0x41065c09, -0x398e870e
)
}
private val h = IntArray(8)
private val r = IntArray(8)
private val w = IntArray(64)
init {
coreReset()
}
override fun coreReset() {
arraycopy(H, 0, h, 0, 8)
}
override fun coreUpdate(chunk: ByteArray) {
arraycopy(h, 0, r, 0, 8)
for (j in 0 until 16) w[j] = chunk.readS32_be(j * 4)
for (j in 16 until 64) {
val s0 = w[j - 15].rotateRight(7) xor w[j - 15].rotateRight(18) xor w[j - 15].ushr(3)
val s1 = w[j - 2].rotateRight(17) xor w[j - 2].rotateRight(19) xor w[j - 2].ushr(10)
w[j] = w[j - 16] + s0 + w[j - 7] + s1
}
for (j in 0 until 64) {
val s1 = r[4].rotateRight(6) xor r[4].rotateRight(11) xor r[4].rotateRight(25)
val ch = r[4] and r[5] xor (r[4].inv() and r[6])
val t1 = r[7] + s1 + ch + K[j] + w[j]
val s0 = r[0].rotateRight(2) xor r[0].rotateRight(13) xor r[0].rotateRight(22)
val maj = r[0] and r[1] xor (r[0] and r[2]) xor (r[1] and r[2])
val t2 = s0 + maj
r[7] = r[6]
r[6] = r[5]
r[5] = r[4]
r[4] = r[3] + t1
r[3] = r[2]
r[2] = r[1]
r[1] = r[0]
r[0] = t1 + t2
}
for (j in 0 until 8) h[j] += r[j]
}
override fun coreDigest(out: ByteArray) {
for (n in out.indices) out[n] = (h[n / 4] ushr (24 - 8 * (n % 4))).toByte()
}
}

View File

@ -41,6 +41,7 @@ kotlin {
api(`kotlinx-serialization-json`)
api(`kotlinx-coroutines-core`)
implementation(`kt-bignum`)
implementation(project(":mirai-core-utils"))
implementation(`kotlinx-serialization-protobuf`)
implementation(`kotlinx-atomicfu`)
@ -108,6 +109,14 @@ kotlin {
}
// Kt bignum
findByName("jvmBaseMain")?.apply {
dependencies {
relocateImplementation(`kt-bignum_relocated`)
}
}
// Ktor
findByName("commonMain")?.apply {

View File

@ -166,6 +166,7 @@ internal open class QQAndroidClient(
var reserveUinInfo: ReserveUinInfo? = null
var t402: ByteArray? = null
lateinit var t104: ByteArray
var t547: ByteArray? = null
}
internal val QQAndroidClient.apkId: ByteArray get() = "com.tencent.mobileqq".toByteArray()

View File

@ -275,6 +275,15 @@ internal fun BytePacketBuilder.t104(
}
}
internal fun BytePacketBuilder.t547(
t547Data: ByteArray
) {
writeShort(0x547)
writeShortLVPacket {
writeFully(t547Data)
}
}
internal fun BytePacketBuilder.t174(
t174Data: ByteArray
) {

View File

@ -210,6 +210,7 @@ internal class WtLogin {
tlvMap[0x161]?.let { bot.client.analysisTlv161(it) }
tlvMap[0x403]?.let { bot.client.randSeed = it }
tlvMap[0x402]?.let { bot.client.t402 = it }
tlvMap[0x546]?.let { bot.client.analysisTlv546(it) }
// tlvMap[0x402]?.let { t402 ->
// bot.client.G = buildPacket {
// writeFully(bot.client.device.guid)

View File

@ -43,7 +43,7 @@ internal object WtLogin10 : WtLoginExt {
0x0810
) {
writeShort(11) // subCommand
writeShort(17)
writeShort(18)
t100(appId, subAppId, client.appClientVersion, client.ssoVersion, mainSigMap)
t10a(client.wLoginSigInfo.tgt)
t116(client.miscBitMap, client.subSigMap)
@ -80,6 +80,7 @@ internal object WtLogin10 : WtLoginExt {
t188(client.device.androidId)
t194(client.device.imsiMd5)
t511()
t202(client.device.wifiBSSID, client.device.wifiSSID)
//t544()
}

View File

@ -26,11 +26,20 @@ internal object WtLogin2 : WtLoginExt {
writeSsoPacket(client, client.subAppId, WtLogin.Login.commandName, sequenceId = sequenceId) {
writeOicqRequestPacket(client, commandId = 0x0810) {
writeShort(2) // subCommand
writeShort(4) // count of TLVs
writeShort(
if (client.t547 == null) {
4
} else {
5
}
) // count of TLVs
t193(ticket)
t8(2052)
t104(client.t104)
t116(client.miscBitMap, client.subSigMap)
client.t547?.let {
t547(it)
}
}
}
}
@ -43,11 +52,20 @@ internal object WtLogin2 : WtLoginExt {
writeSsoPacket(client, client.subAppId, WtLogin.Login.commandName, sequenceId = sequenceId) {
writeOicqRequestPacket(client, commandId = 0x0810) {
writeShort(2) // subCommand
writeShort(4) // count of TLVs
writeShort(
if (client.t547 == null) {
4
} else {
5
}
) // count of TLVs
t2(captchaAnswer, captchaSign, 0)
t8(2052)
t104(client.t104)
t116(client.miscBitMap, client.subSigMap)
client.t547?.let {
t547(it)
}
}
}
}

View File

@ -9,6 +9,9 @@
package net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin
import com.ionspin.kotlin.bignum.integer.BigInteger
import com.ionspin.kotlin.bignum.integer.util.fromTwosComplementByteArray
import com.ionspin.kotlin.bignum.integer.util.toTwosComplementByteArray
import io.ktor.utils.io.core.*
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.network.LoginExtraData
@ -171,6 +174,126 @@ internal interface WtLoginExt { // so as not to register to global extension
this.t150 = Tlv(t150)
}
fun QQAndroidClient.analysisTlv546(t546: ByteArray) {
val version: Byte
val algorithmType: Byte
val hashType: Byte
val maxIndex: Short
val reserveBytes: ByteArray
val inputBigNumArr: ByteArray
val targetHashArr: ByteArray
val reserveHashArr: ByteArray
var resultArr: ByteArray = EMPTY_BYTE_ARRAY;
var costTimeMS: Int = 0;
var recursiveDepth: Int = 0;
var failed = false
fun getPadRemaining(bigNumArr: ByteArray, bound: Short): Int {
if (bound > 32) {
return 1
}
var maxLoopCount = 255
var index = 0
while (maxLoopCount >= 0 && index < bound) {
if (bigNumArr[maxLoopCount / 8].toInt() and (1 shl maxLoopCount) % 8 != 0) {
return 2
}
maxLoopCount--
index++
}
return 0
}
fun calcType1(bigNumArrIn: ByteArray, maxLength: Short) {
var bigIntArrClone = bigNumArrIn.copyOf()
val originLength = bigIntArrClone.size
var bigInteger = BigInteger.fromTwosComplementByteArray(bigIntArrClone)
while (true) {
if (getPadRemaining(bigIntArrClone.sha256().copyOf(32), maxLength) == 0) {
resultArr = bigIntArrClone
return
}
recursiveDepth++
bigInteger = bigInteger.add(BigInteger.ONE)
bigIntArrClone = bigInteger.toTwosComplementByteArray()
if (bigIntArrClone.size > originLength) {
failed = true
return
}
}
}
fun calcType2(bigNumArrIn: ByteArray, hashTarget: ByteArray) {
var bigIntArrClone = bigNumArrIn.copyOf()
val originLength = bigIntArrClone.size
var bigInteger = BigInteger.fromTwosComplementByteArray(bigIntArrClone)
while (true) {
if (bigIntArrClone.sha256().copyOf(32).contentEquals(hashTarget)) {
resultArr = bigIntArrClone
return
}
recursiveDepth++
bigInteger = bigInteger.add(BigInteger.ONE)
bigIntArrClone = bigInteger.toTwosComplementByteArray()
if (bigIntArrClone.size > originLength) {
failed = true
return
}
}
}
t546.toReadPacket().apply {
version = readByte()
algorithmType = readByte()
hashType = readByte()
readByte() // Ignore resultStatus since it's useless
maxIndex = readShort()
reserveBytes = readBytes(2)
inputBigNumArr = readBytes(readShort().toInt())
targetHashArr = readBytes(readShort().toInt())
reserveHashArr = readBytes(readShort().toInt())
}
val startTimeMS: Long = currentTimeMillis()
costTimeMS = 0
recursiveDepth = 0
if (hashType == 1.toByte()) {
bot.logger.info("Calculating type $algorithmType PoW, it can take some time....")
when (algorithmType.toInt()) {
1 -> calcType1(inputBigNumArr, maxIndex)
2 -> calcType2(inputBigNumArr, targetHashArr)
else -> {
failed = true
bot.logger.warning("Unsupported tlv546 algorithm type:${algorithmType}")
}
}
} else {
failed = true
bot.logger.warning("Unsupported tlv546 hash type:${hashType}")
}
if (!failed) {
costTimeMS = (currentTimeMillis() - startTimeMS).toInt()
bot.logger.info("Got PoW result, cost: $costTimeMS ms")
this.t547 = buildPacket {
writeByte(version)
writeByte(algorithmType)
writeByte(hashType)
writeByte(1) //resultStatus
writeShort(maxIndex)
writeFully(reserveBytes)
writeShortLVByteArray(inputBigNumArr)
writeShortLVByteArray(targetHashArr)
writeShortLVByteArray(reserveHashArr)
writeShortLVByteArray(resultArr)
writeInt(costTimeMS)
writeInt(recursiveDepth)
}.readBytes()
} else {
bot.logger.warning("Failed to get PoW result, login may fail with error 0x6!")
}
}
fun QQAndroidClient.analysisTlv161(t161: ByteArray) {
val tlv = t161.toReadPacket().apply { discardExact(2) }.withUse { _readTLVMap() }

View File

@ -23,6 +23,7 @@ class CoreShadowRelocationTest : AbstractTest() {
private const val KtorOkHttp = "io.ktor.client.engine.okhttp.OkHttp"
private const val OkHttp = "okhttp3.OkHttp"
private const val OkIO = "okio.ByteString"
private const val BigInteger = "com.ionspin.kotlin.bignum.integer.BigInteger"
fun relocated(string: String): String {
return "net.mamoe.mirai.internal.deps.$string"
@ -38,6 +39,7 @@ class CoreShadowRelocationTest : AbstractTest() {
-both(`ktor-client-okhttp`)
-both(`okhttp3-okhttp`)
-both(okio)
-both(`kt-bignum`)
}
applyCodeFragment(fragment)
buildFile.appendText(
@ -59,6 +61,7 @@ class CoreShadowRelocationTest : AbstractTest() {
-both(`ktor-client-okhttp`)
-both(`okhttp3-okhttp`)
-both(okio)
-both(`kt-bignum`)
+relocated(`ExternalResource-input`)
}
applyCodeFragment(fragment)
@ -82,6 +85,7 @@ class CoreShadowRelocationTest : AbstractTest() {
+relocated(`okhttp3-okhttp`)
+relocated(okio)
+relocated(`ExternalResource-input`)
+relocated(`kt-bignum`)
}
applyCodeFragment(fragment)
buildFile.appendText(
@ -106,6 +110,7 @@ class CoreShadowRelocationTest : AbstractTest() {
+relocated(`ktor-client-okhttp`)
+relocated(`okhttp3-okhttp`)
+relocated(okio)
+relocated(`kt-bignum`)
}
applyCodeFragment(fragment)
buildFile.appendText(
@ -130,6 +135,7 @@ class CoreShadowRelocationTest : AbstractTest() {
-both(`ktor-client-okhttp`)
-both(`okhttp3-okhttp`)
-both(okio)
-both(`kt-bignum`)
// +relocated(`ExternalResource-input`) // Will fail with no class def found error because there is no runtime ktor-io
}
applyCodeFragment(fragment)
@ -155,6 +161,7 @@ class CoreShadowRelocationTest : AbstractTest() {
+relocated(`okhttp3-okhttp`)
+relocated(okio)
+relocated(`ExternalResource-input`)
+relocated(`kt-bignum`)
}
applyCodeFragment(fragment)
@ -231,6 +238,7 @@ class CoreShadowRelocationTest : AbstractTest() {
val `ktor-client-okhttp` = ClassTestCase("ktor-client-core OkHttp", KtorOkHttp)
val `okhttp3-okhttp` = ClassTestCase("okhttp3 OkHttp", OkHttp)
val okio = ClassTestCase("okio ByteString", OkIO)
val `kt-bignum` = ClassTestCase("kt-bignum BigInteger", BigInteger)
val `ExternalResource-input` =
FunctionTestCase(
"ExternalResource_input",