From 905823b94b9bf94493ed35dc66d82805568b7b38 Mon Sep 17 00:00:00 2001 From: Him188 Date: Wed, 29 Jan 2020 23:53:18 +0800 Subject: [PATCH] Slider captcha solver --- .../network/QQAndroidBotNetworkHandler.kt | 14 +++--- .../protocol/packet/OutgoingPacketAndroid.kt | 46 ----------------- .../qqandroid/network/protocol/packet/Tlv.kt | 9 ++++ .../protocol/packet/login/LoginPacket.kt | 49 ++++++++++++++----- .../mamoe/mirai/utils/defaultLoginSolver.kt | 2 +- .../net.mamoe.mirai/utils/BotConfiguration.kt | 2 +- .../mirai/utils/DefaultCaptchaSolverJvm.kt | 23 +++++---- .../net/mamoe/mirai/demo/MiraiService.kt | 2 +- 8 files changed, 71 insertions(+), 76 deletions(-) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt index 0239ecebe..c14b0795c 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt @@ -53,19 +53,21 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler is Captcha -> when (response) { is Captcha.Picture -> { - bot.logger.info("需要图片验证码") var result = bot.configuration.loginSolver.onSolvePicCaptcha(bot, response.data) - if (result === null || result.length != 4) { + if (result == null || result.length != 4) { //refresh captcha result = "ABCD" } - bot.logger.info("提交验证码") - response = LoginPacket.SubCommand2(bot.client, response.sign, result).sendAndExpect() + response = LoginPacket.SubCommand2.SubmitPictureCaptcha(bot.client, response.sign, result).sendAndExpect() continue@mainloop } is Captcha.Slider -> { - bot.logger.info("需要滑动验证码") - TODO("滑动验证码") + var ticket = bot.configuration.loginSolver.onSolveSliderCaptcha(bot, response.url) + if (ticket == null) { + ticket = "" + } + response = LoginPacket.SubCommand2.SubmitSliderCaptcha(bot.client, ticket).sendAndExpect() + continue@mainloop } } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/OutgoingPacketAndroid.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/OutgoingPacketAndroid.kt index d10d2c8f6..509b62af7 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/OutgoingPacketAndroid.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/OutgoingPacketAndroid.kt @@ -264,52 +264,6 @@ internal inline fun BytePacketBuilder.writeSsoPacket( writeIntLVPacket(lengthOffset = { it + 4 }, builder = body) } -internal inline fun BytePacketBuilder.writeSmsSsoPacket( - client: QQAndroidClient, - subAppId: Long, - commandName: String, - extraData: ByteReadPacket = BRP_STUB, - sequenceId: Int, - body: BytePacketBuilder.() -> Unit -) { - writeIntLVPacket(lengthOffset = { it + 4 }) { - writeInt(sequenceId) - writeInt(subAppId.toInt()) - writeInt(subAppId.toInt()) - writeHex("00 00 00 00 00 00 00 00 00 00 01 00") - if (extraData === BRP_STUB) { - writeInt(0x04) - } else { - writeInt((extraData.remaining + 4).toInt()) - writePacket(extraData) - } - commandName.let { - writeInt(it.length + 4) - writeStringUtf8(it) - } - - writeInt(4 + 4) - writeInt(45112203) // 02 B0 5B 8B - - client.device.imei.let { - writeInt(it.length + 4) - writeStringUtf8(it) - } - - writeInt(4) - - client.ksid.let { - writeShort((it.size + 2).toShort()) - writeFully(it) - } - - writeInt(4) - } - - // body - writeIntLVPacket(lengthOffset = { it + 4 }, builder = body) -} - /** * Writes a request packet diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt index 873b9aadb..d24d6ac56 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt @@ -566,6 +566,15 @@ fun BytePacketBuilder.t188( } shouldEqualsTo 16 } +fun BytePacketBuilder.t193( + ticket: String +) { + writeShort(0x193) + writeShortLVPacket { + writeFully(ticket.toByteArray()) + } +} + fun BytePacketBuilder.t194( imsiMd5: ByteArray ) { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/LoginPacket.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/LoginPacket.kt index 98a6db16a..fc71737ce 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/LoginPacket.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/LoginPacket.kt @@ -23,12 +23,30 @@ import net.mamoe.mirai.utils.io.discardExact @UseExperimental(ExperimentalUnsignedTypes::class) internal object LoginPacket : PacketFactory("wtlogin.login") { + /** + * 提交验证码 + */ object SubCommand2 { private const val appId = 16L private const val subAppId = 537062845L - @UseExperimental(MiraiInternalAPI::class) - operator fun invoke( + fun SubmitSliderCaptcha( + client: QQAndroidClient, + ticket: String + ): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId -> + writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) { + writeOicqRequestPacket(client, EncryptMethodECDH7(client.ecdh), 0x0810) { + writeShort(2) // subCommand + writeShort(4) // count of TLVs + t193(ticket) + t8(2052) + t104(client.t104) + t116(150470524, 66560) + } + } + } + + fun SubmitPictureCaptcha( client: QQAndroidClient, captchaSign: ByteArray, captchaAnswer: String @@ -36,7 +54,7 @@ internal object LoginPacket : PacketFactory("wt writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) { writeOicqRequestPacket(client, EncryptMethodECDH7(client.ecdh), 0x0810) { writeShort(2) // subCommand - writeShort(4) // count of TLVs, probably ignored by server? + writeShort(4) // count of TLVs t2(captchaAnswer, captchaSign, 0) t8(2052) t104(client.t104) @@ -69,6 +87,9 @@ internal object LoginPacket : PacketFactory("wt } } + /** + * 提交 SMS + */ object SubCommand7 { private const val appId = 16L private const val subAppId = 537062845L @@ -79,7 +100,7 @@ internal object LoginPacket : PacketFactory("wt t402: ByteArray, phoneNumber: String ): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId -> - writeSmsSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) { + writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId, unknownHex = "01 00 00 00 00 00 00 00 00 00 01 00") { writeOicqRequestPacket(client, EncryptMethodECDH7(client.ecdh), 0x0810) { writeShort(8) // subCommand writeShort(6) // count of TLVs, probably ignored by server?TODO @@ -96,6 +117,9 @@ internal object LoginPacket : PacketFactory("wt } } + /** + * 密码登录 + */ object SubCommand9 { private const val appId = 16L private const val subAppId = 537062845L @@ -257,8 +281,7 @@ internal object LoginPacket : PacketFactory("wt sealed class Captcha : LoginPacketResponse() { class Slider( - val data: IoBuffer, - val sign: ByteArray + val url: String ) : Captcha() { override fun toString(): String { return "LoginPacketResponse.Captcha.Slider" @@ -340,10 +363,12 @@ internal object LoginPacket : PacketFactory("wt @UseExperimental(MiraiDebugAPI::class) private fun onSolveLoginCaptcha(tlvMap: TlvMap, bot: QQAndroidBot): LoginPacketResponse.Captcha { // val ret = tlvMap[0x104]?.let { println(it.toUHexString()) } - println() - val question = tlvMap[0x165] ?: error("CAPTCHA QUESTION UNKNOWN") - when (question[18].toInt()) { - 0x36 -> { + tlvMap[0x192]?.let { + bot.client.t104 = tlvMap.getOrFail(0x104) + return LoginPacketResponse.Captcha.Slider(it.encodeToString()) + } + tlvMap[0x165]?.let { question -> + if (question[18].toInt() == 0x36) { //图片验证 DebugLogger.debug("是一个图片验证码") bot.client.t104 = tlvMap.getOrFail(0x104) @@ -356,8 +381,10 @@ internal object LoginPacket : PacketFactory("wt sign = sign ) } - else -> error("UNKNOWN CAPTCHA QUESTION: $question") + else error("UNKNOWN CAPTCHA QUESTION: $question") } + + error("UNKNOWN CAPTCHA") } @UseExperimental(MiraiDebugAPI::class) diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/defaultLoginSolver.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/defaultLoginSolver.kt index ba151f5ba..dc631dfc0 100644 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/defaultLoginSolver.kt +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/defaultLoginSolver.kt @@ -11,7 +11,7 @@ actual var defaultLoginSolver: LoginSolver = object : LoginSolver() { error("should be implemented manually by you") } - override suspend fun onSolveSliderCaptcha(bot: Bot, data: IoBuffer): String? { + override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? { error("should be implemented manually by you") } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt index 8cbed1ba0..b86e4607e 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt @@ -11,7 +11,7 @@ import kotlin.jvm.JvmStatic abstract class LoginSolver { abstract suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String? - abstract suspend fun onSolveSliderCaptcha(bot: Bot, data: IoBuffer): String? + abstract suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? abstract suspend fun onGetPhoneNumber(): String diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/DefaultCaptchaSolverJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/DefaultCaptchaSolverJvm.kt index 47b9f8c57..5d0c049a3 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/DefaultCaptchaSolverJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/DefaultCaptchaSolverJvm.kt @@ -34,38 +34,41 @@ class DefaultLoginSolver : LoginSolver() { val tempFile: File = createTempFile(suffix = ".png").apply { deleteOnExit() } withContext(Dispatchers.IO) { tempFile.createNewFile() - MiraiLogger.info("需要验证码登录, 验证码为 4 字母") + bot.logger.info("需要图片验证码登录, 验证码为 4 字母") try { tempFile.writeChannel().use { writeFully(data) } - MiraiLogger.info("将会显示字符图片. 若看不清字符图片, 请查看文件 ${tempFile.absolutePath}") + bot.logger.info("将会显示字符图片. 若看不清字符图片, 请查看文件 ${tempFile.absolutePath}") } catch (e: Exception) { - MiraiLogger.info("无法写出验证码文件(${e.message}), 请尝试查看以上字符图片") + bot.logger.info("无法写出验证码文件(${e.message}), 请尝试查看以上字符图片") } tempFile.inputStream().use { val img = ImageIO.read(it) if (img == null) { - MiraiLogger.info("无法创建字符图片. 请查看文件") + bot.logger.info("无法创建字符图片. 请查看文件") } else { - MiraiLogger.info(img.createCharImg()) + bot.logger.info(img.createCharImg()) } } } - MiraiLogger.info("请输入 4 位字母验证码. 若要更换验证码, 请直接回车") + bot.logger.info("请输入 4 位字母验证码. 若要更换验证码, 请直接回车") return readLine()?.takeUnless { it.isEmpty() || it.length != 4 } } } - override suspend fun onSolveSliderCaptcha(bot: Bot, data: IoBuffer): String? { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? { + bot.logger.info("需要滑动验证码") + bot.logger.info("请在任意浏览器中打开以下链接并完成验证码. ") + bot.logger.info(url) + return readLine() } override suspend fun onGetPhoneNumber(): String { loginSolverLock.withLock { - while (true){ + while (true) { MiraiLogger.info("请输入你的手机号码") val var0 = readLine() - if(var0!==null && var0.length > 10){ + if (var0 !== null && var0.length > 10) { return var0; } } diff --git a/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MiraiService.kt b/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MiraiService.kt index fa5e52444..5574d630b 100644 --- a/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MiraiService.kt +++ b/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MiraiService.kt @@ -53,7 +53,7 @@ class MiraiService : Service() { return mCaptchaDeferred.await() } - override suspend fun onSolveSliderCaptcha(bot: Bot, data: IoBuffer): String? { + override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? { TODO("not implemented") }