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 e141e1b4c..19a3d23c7 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
@@ -33,37 +33,53 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
         launch(CoroutineName("Incoming Packet Receiver")) { processReceive() }
 
         bot.logger.info("Trying login")
-        when (val response: LoginPacket.LoginPacketResponse = LoginPacket.SubCommand9(bot.client).sendAndExpect()) {
-            is UnsafeLogin -> {
-                bot.logger.info("Login unsuccessful, device auth is needed")
-                bot.logger.info("登陆失败, 原因为非常用设备登陆")
-                bot.logger.info("Open the following URL in QQ browser and complete the verification")
-                bot.logger.info("将下面这个链接在QQ浏览器中打开并完成认证后尝试再次登陆")
-                bot.logger.info(response.url)
-                return
-            }
+        var response: LoginPacket.LoginPacketResponse = LoginPacket.SubCommand9(bot.client).sendAndExpect()
+        mainloop@ while (true) {
+            when (response) {
+                is UnsafeLogin -> {
+                    bot.logger.info("Login unsuccessful, device auth is needed")
+                    bot.logger.info("登陆失败, 原因为非常用设备登陆")
+                    bot.logger.info("Open the following URL in QQ browser and complete the verification")
+                    bot.logger.info("将下面这个链接在QQ浏览器中打开并完成认证后尝试再次登陆")
+                    bot.logger.info(response.url)
+                    return
+                }
 
-            is Captcha -> when (response) {
-                is Captcha.Picture -> {
-                    bot.logger.info("需要图片验证码")
-                    var result = bot.configuration.captchaSolver.invoke(bot, response.data)
-                    if (result === null || result.length != 4) {
-                        //refresh captcha
-                        result = "ABCD"
+                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) {
+                            //refresh captcha
+                            result = "ABCD"
+                        }
+                        bot.logger.info("提交验证码")
+                        response = LoginPacket.SubCommand2(bot.client, response.sign, result).sendAndExpect()
+                        continue@mainloop
+                    }
+                    is Captcha.Slider -> {
+                        bot.logger.info("需要滑动验证码")
+                        TODO("滑动验证码")
                     }
-                    bot.logger.info("提交验证码")
-                    val captchaResponse: LoginPacket.LoginPacketResponse =
-                        LoginPacket.SubCommand2(bot.client, response.sign, result).sendAndExpect()
                 }
-                is Captcha.Slider -> {
-                    bot.logger.info("需要滑动验证码")
+
+                is Error -> error(response.toString())
+
+                is SMSVerifyCodeNeeded -> {
+                    val result = bot.configuration.loginSolver.onGetPhoneNumber()
+                    response = LoginPacket.SubCommand7(
+                        bot.client,
+                        response.t174,
+                        response.t402,
+                        result
+                    ).sendAndExpect()
+                    continue@mainloop
                 }
-            }
 
-            is Error -> error(response.toString())
-
-            is Success -> {
-                bot.logger.info("Login successful")
+                is Success -> {
+                    bot.logger.info("Login successful")
+                    break@mainloop
+                }
             }
         }
 
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 008c8f70c..241d8e34d 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
@@ -142,6 +142,7 @@ fun BytePacketBuilder.t116(
     }
 }
 
+
 fun BytePacketBuilder.t100(
     appId: Long = 16,
     subAppId: Long = 537062845,
@@ -193,6 +194,44 @@ fun BytePacketBuilder.t104(
     }
 }
 
+fun BytePacketBuilder.t174(
+    t174Data: ByteArray
+) {
+    writeShort(0x174)
+    writeShortLVPacket {
+        writeFully(t174Data)
+    }
+}
+
+fun BytePacketBuilder.t19e(
+    value: Int = 0
+) {
+    writeShort(0x19e)
+    writeShortLVPacket {
+        writeShort(1)
+        writeByte(value.toByte())
+    }
+}
+
+fun BytePacketBuilder.t17c(
+    t17cData: ByteArray
+) {
+    writeShort(0x17c)
+    writeShortLVPacket {
+        writeShort(t17cData.size.toShort())
+        writeFully(t17cData)
+    }
+}
+
+fun BytePacketBuilder.t401(
+    t401Data: ByteArray
+) {
+    writeShort(0x401)
+    writeShortLVPacket {
+        writeFully(t401Data)
+    }
+}
+
 /**
  * @param apkId application.getPackageName().getBytes()
  */
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 12a68b40f..953292005 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
@@ -11,12 +11,9 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.*
 import net.mamoe.mirai.qqandroid.utils.GuidSource
 import net.mamoe.mirai.qqandroid.utils.MacOrAndroidIdChangeFlag
 import net.mamoe.mirai.qqandroid.utils.guidFlag
-import net.mamoe.mirai.utils.MiraiDebugAPI
-import net.mamoe.mirai.utils.MiraiInternalAPI
+import net.mamoe.mirai.utils.*
 import net.mamoe.mirai.utils.cryptor.contentToString
 import net.mamoe.mirai.utils.cryptor.decryptBy
-import net.mamoe.mirai.utils.currentTimeMillis
-import net.mamoe.mirai.utils.currentTimeSeconds
 import net.mamoe.mirai.utils.io.*
 import net.mamoe.mirai.utils.io.discardExact
 
@@ -49,6 +46,33 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse>("wt
         }
     }
 
+    object SubCommand7 {
+        private const val appId = 16L
+        private const val subAppId = 537062845L
+
+        @UseExperimental(MiraiInternalAPI::class)
+        operator fun invoke(
+            client: QQAndroidClient,
+            t174: ByteArray,
+            t402: ByteArray,
+            phoneNumber: String
+        ): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
+            writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) {
+                writeOicqRequestPacket(client, EncryptMethodECDH7(client.ecdh), 0x0810) {
+                    writeShort(7) // subCommand
+                    writeShort(7) // count of TLVs, probably ignored by server?TODO
+                    t8(2052)
+                    t104(client.t104)
+                    t116(150470524, 66560)
+                    t174(t174)
+                    t17c(phoneNumber.toByteArray())
+                    t401(md5(client.device.guid + "1234567890123456".toByteArray() + t402))
+                    t19e(0)//==tlv408
+                }
+            }
+        }
+    }
+
     object SubCommand9 {
         private const val appId = 16L
         private const val subAppId = 537062845L
@@ -225,7 +249,7 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse>("wt
 
         class UnsafeLogin(val url: String) : LoginPacketResponse()
 
-        class DeviceLockLogin() : LoginPacketResponse()
+        class SMSVerifyCodeNeeded(val t174: ByteArray, val t402: ByteArray) : LoginPacketResponse()
     }
 
     @InternalAPI
@@ -251,17 +275,18 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse>("wt
             1, 15 -> onErrorMessage(tlvMap)
             2 -> onSolveLoginCaptcha(tlvMap, bot)
             -96 -> onUnsafeDeviceLogin(tlvMap, bot)
-            -52 -> onDeviceLockLogin(tlvMap, bot)
+            -52 -> onSMSVerifyNeeded(tlvMap, bot)
             else -> error("unknown login result type: $type")
         }
     }
 
 
-    private fun onDeviceLockLogin(tlvMap: Map<Int, ByteArray>, bot: QQAndroidBot): LoginPacketResponse.DeviceLockLogin {
-        println(tlvMap[0x104]!!.toUHexString())
-        println(tlvMap[0x402]!!.toUHexString())
-        println(tlvMap[0x403]!!.toUHexString())
-        return LoginPacketResponse.DeviceLockLogin();
+    private fun onSMSVerifyNeeded(
+        tlvMap: Map<Int, ByteArray>,
+        bot: QQAndroidBot
+    ): LoginPacketResponse.SMSVerifyCodeNeeded {
+        bot.client.t104 = tlvMap[0x104]!!
+        return LoginPacketResponse.SMSVerifyCodeNeeded(tlvMap[0x174] ?: EMPTY_BYTE_ARRAY, tlvMap[0x402]!!)
     }
 
     private fun onUnsafeDeviceLogin(tlvMap: Map<Int, ByteArray>, bot: QQAndroidBot): LoginPacketResponse.UnsafeLogin {
diff --git a/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/TIMPCBotNetworkHandler.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/TIMPCBotNetworkHandler.kt
index 099a563e6..c6fea2d61 100644
--- a/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/TIMPCBotNetworkHandler.kt
+++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/TIMPCBotNetworkHandler.kt
@@ -389,7 +389,7 @@ internal class TIMPCBotNetworkHandler internal constructor(coroutineContext: Cor
                             close()
                             return
                         }
-                        val code = configuration.captchaSolver(bot, captchaCache!!)
+                        val code = configuration.loginSolver(bot, captchaCache!!)
 
                         this.captchaCache = null
                         if (code == null || code.length != 4) {
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 fc18b658b..8cbed1ba0 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
@@ -7,16 +7,21 @@ import kotlin.coroutines.EmptyCoroutineContext
 import kotlin.jvm.JvmStatic
 
 /**
- * 验证码处理器. 需挂起(阻塞)直到处理完成验证码.
- *
- * 返回长度为 4 的验证码. 为空则刷新验证码
  */
-typealias CaptchaSolver = suspend Bot.(IoBuffer) -> String?
+abstract class LoginSolver {
+    abstract suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String?
+
+    abstract suspend fun onSolveSliderCaptcha(bot: Bot, data: IoBuffer): String?
+
+    abstract suspend fun onGetPhoneNumber(): String
+
+    abstract suspend fun onGetSMSVerifyCode(): String
+}
 
 /**
  * 在各平台实现的默认的验证码处理器.
  */
-expect var DefaultCaptchaSolver: CaptchaSolver
+expect var defaultLoginSolver: LoginSolver
 
 /**
  * 网络和连接配置
@@ -70,7 +75,7 @@ class BotConfiguration {
     /**
      * 验证码处理器
      */
-    var captchaSolver: CaptchaSolver = DefaultCaptchaSolver
+    var loginSolver: LoginSolver = defaultLoginSolver
     /**
      * 登录完成后几秒会收到好友消息的历史记录,
      * 这些历史记录不会触发事件.
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 b891eeb84..255ef5bc1 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
@@ -10,7 +10,9 @@ import kotlinx.coroutines.io.reader
 import kotlinx.coroutines.sync.Mutex
 import kotlinx.coroutines.sync.withLock
 import kotlinx.coroutines.withContext
+import kotlinx.io.core.IoBuffer
 import kotlinx.io.core.use
+import net.mamoe.mirai.Bot
 import java.awt.Image
 import java.awt.image.BufferedImage
 import java.io.File
@@ -23,31 +25,68 @@ import kotlin.coroutines.CoroutineContext
  *
  * 可被修改, 除覆盖配置外全局生效.
  */
-actual var DefaultCaptchaSolver: CaptchaSolver = {
-    captchaLock.withLock {
-        val tempFile: File = createTempFile(suffix = ".png").apply { deleteOnExit() }
-        withContext(Dispatchers.IO) {
-            tempFile.createNewFile()
-            MiraiLogger.info("需要验证码登录, 验证码为 4 字母")
-            try {
-                tempFile.writeChannel().use { writeFully(it) }
-                MiraiLogger.info("将会显示字符图片. 若看不清字符图片, 请查看文件 ${tempFile.absolutePath}")
-            } catch (e: Exception) {
-                MiraiLogger.info("无法写出验证码文件(${e.message}), 请尝试查看以上字符图片")
-            }
+actual var defaultLoginSolver: LoginSolver = DefaultLoginSolver()
 
-            tempFile.inputStream().use {
-                val img = ImageIO.read(it)
-                if (img == null) {
-                    MiraiLogger.info("无法创建字符图片. 请查看文件")
-                } else {
-                    MiraiLogger.info(img.createCharImg())
+
+class DefaultLoginSolver(): LoginSolver(){
+    override suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String? {
+        loginSolverLock.withLock {
+            val tempFile: File = createTempFile(suffix = ".png").apply { deleteOnExit() }
+            withContext(Dispatchers.IO) {
+                tempFile.createNewFile()
+                MiraiLogger.info("需要验证码登录, 验证码为 4 字母")
+                try {
+                    tempFile.writeChannel().use { writeFully(data) }
+                    MiraiLogger.info("将会显示字符图片. 若看不清字符图片, 请查看文件 ${tempFile.absolutePath}")
+                } catch (e: Exception) {
+                    MiraiLogger.info("无法写出验证码文件(${e.message}), 请尝试查看以上字符图片")
+                }
+
+                tempFile.inputStream().use {
+                    val img = ImageIO.read(it)
+                    if (img == null) {
+                        MiraiLogger.info("无法创建字符图片. 请查看文件")
+                    } else {
+                        MiraiLogger.info(img.createCharImg())
+                    }
+                }
+            }
+            MiraiLogger.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 onGetPhoneNumber(): String {
+        loginSolverLock.withLock {
+            while (true){
+                MiraiLogger.info("请输入你的手机号码")
+                val var0 = readLine()
+                if(var0!==null && var0.length > 10){
+                    return var0;
                 }
             }
         }
-        MiraiLogger.info("请输入 4 位字母验证码. 若要更换验证码, 请直接回车")
-        readLine()?.takeUnless { it.isEmpty() || it.length != 4 }
+        return "";
     }
+
+    override suspend fun onGetSMSVerifyCode(): String {
+        loginSolverLock.withLock {
+            while (true){
+                MiraiLogger.info("请输入你刚刚收到的手机验证码[6位数字]")
+                val var0 = readLine()
+                if(var0!==null && var0.length == 6){
+                    return var0;
+                }
+            }
+        }
+        return "";
+    }
+
+
 }
 
 // Copied from Ktor CIO
@@ -62,7 +101,7 @@ private fun File.writeChannel(
 }.channel
 
 
-private val captchaLock = Mutex()
+private val loginSolverLock = Mutex()
 
 /**
  * @author NaturalHG