Slider captcha solver

This commit is contained in:
Him188 2020-01-29 23:53:18 +08:00
parent 817f3b02a1
commit 905823b94b
8 changed files with 71 additions and 76 deletions

View File

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

View File

@ -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

View File

@ -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
) {

View File

@ -23,12 +23,30 @@ import net.mamoe.mirai.utils.io.discardExact
@UseExperimental(ExperimentalUnsignedTypes::class)
internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse>("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<LoginPacket.LoginPacketResponse>("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<LoginPacket.LoginPacketResponse>("wt
}
}
/**
* 提交 SMS
*/
object SubCommand7 {
private const val appId = 16L
private const val subAppId = 537062845L
@ -79,7 +100,7 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse>("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<LoginPacket.LoginPacketResponse>("wt
}
}
/**
* 密码登录
*/
object SubCommand9 {
private const val appId = 16L
private const val subAppId = 537062845L
@ -257,8 +281,7 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse>("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<LoginPacket.LoginPacketResponse>("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<LoginPacket.LoginPacketResponse>("wt
sign = sign
)
}
else -> error("UNKNOWN CAPTCHA QUESTION: $question")
else error("UNKNOWN CAPTCHA QUESTION: $question")
}
error("UNKNOWN CAPTCHA")
}
@UseExperimental(MiraiDebugAPI::class)

View File

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

View File

@ -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

View File

@ -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;
}
}

View File

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