Session finished

This commit is contained in:
Him188moe 2019-08-31 19:16:52 +08:00
parent 8606c4c778
commit 4508c66225
17 changed files with 332 additions and 103 deletions

View File

@ -21,9 +21,12 @@ import java.util.LinkedList;
import java.util.Scanner;
public class MiraiServer {
@Getter
private static MiraiServer instance;
public static MiraiServer getInstance() {
return instance;
}
//mirai version
private final static String MIRAI_VERSION = "1.0.0";
@ -35,7 +38,7 @@ public class MiraiServer {
private boolean unix;
@Getter//file path
private File parentFolder;
public File parentFolder;
@Getter
MiraiEventManager eventManager;

View File

@ -1,18 +1,24 @@
package net.mamoe.mirai.network
import io.netty.channel.Channel
import net.mamoe.mirai.MiraiServer
import net.mamoe.mirai.network.packet.client.ClientPacket
import net.mamoe.mirai.network.packet.client.login.*
import net.mamoe.mirai.network.packet.client.session.ClientHandshake1Packet
import net.mamoe.mirai.network.packet.client.session.ClientLoginStatusPacket
import net.mamoe.mirai.network.packet.client.session.ClientSKeyRequestPacket
import net.mamoe.mirai.network.packet.client.writeHex
import net.mamoe.mirai.network.packet.client.writeRandom
import net.mamoe.mirai.network.packet.server.ServerPacket
import net.mamoe.mirai.network.packet.server.login.*
import net.mamoe.mirai.network.packet.server.security.ServerSessionKeyResponsePacket
import net.mamoe.mirai.network.packet.server.security.ServerSessionKeyResponsePacketEncrypted
import net.mamoe.mirai.network.packet.server.security.*
import net.mamoe.mirai.network.packet.server.touch.ServerTouchResponsePacket
import net.mamoe.mirai.network.packet.server.touch.ServerTouchResponsePacketEncrypted
import net.mamoe.mirai.task.MiraiTaskManager
import net.mamoe.mirai.util.*
import net.mamoe.mirai.utils.MiraiLogger
import java.io.ByteArrayInputStream
import java.io.FileOutputStream
import java.net.DatagramPacket
import java.net.DatagramSocket
import java.net.InetSocketAddress
@ -24,6 +30,7 @@ import java.util.*
* @author Him188moe
*/
class RobotNetworkHandler(val number: Int, private val password: String) {
private var sequence: Int = 0
private var channel: Channel? = null
@ -36,16 +43,23 @@ class RobotNetworkHandler(val number: Int, private val password: String) {
private lateinit var serverAddress: InetSocketAddress
private lateinit var token00BA: ByteArray
private lateinit var token00BA: ByteArray //这些数据全部是login用的
private lateinit var token0825: ByteArray
private var loginTime: Int = 0
private lateinit var loginIP: String
private var tgtgtKey: ByteArray? = null
private var tlv0105: ByteArray
private lateinit var _0828_rec_decr_key: ByteArray
private lateinit var sessionKey: ByteArray//这两个是登录成功后得到的
private lateinit var sKey: String
/**
* Kind of key, similar to sessionKey
* Used to access web API(for friends list etc.)
*/
private var tlv0105: ByteArray
private lateinit var cookies: String
private var gtk: Int = 0
init {
tlv0105 = lazyEncode {
@ -57,11 +71,6 @@ class RobotNetworkHandler(val number: Int, private val password: String) {
}
}
private lateinit var sessionKey: ByteArray
/**
* Kind of key, similar to sessionKey
*/
private lateinit var _0828_rec_decr_key: ByteArray
@ExperimentalUnsignedTypes
private var md5_32: ByteArray = getRandomKey(32)
@ -100,6 +109,12 @@ class RobotNetworkHandler(val number: Int, private val password: String) {
sendPacket(ClientLoginVerificationCodePacket(this.number, this.token0825, this.sequence, this.token00BA))
}
with(MiraiServer.getInstance().parentFolder + "verifyCode.png") {
ByteArrayInputStream(packet.verifyCode).transferTo(FileOutputStream(this))
println("验证码已写入到 " + this.path)
}
}
is ServerLoginResponseSuccessPacket -> {
@ -148,12 +163,30 @@ class RobotNetworkHandler(val number: Int, private val password: String) {
is ServerSessionKeyResponsePacket -> {
this.sessionKey = packet.sessionKey
this.tlv0105 = packet.tlv0105
sendPacket(ClientLoginStatusPacket(this.number, sessionKey, ClientLoginStatus.ONLINE))
}
is ServerLoginSuccessPacket -> {
sendPacket(ClientSKeyRequestPacket(this.number, this.sessionKey))
}
is ServerSKeyResponsePacket -> {
this.sKey = packet.sKey
this.cookies = "uin=o" + this.number + ";skey=" + this.sKey + ";"
MiraiTaskManager.getInstance().repeatingTask({
TODO("时钟_refreshSkey")
}, 1800000)
this.gtk = getGTK(sKey)
sendPacket(ClientHandshake1Packet(this.number, this.sessionKey))
}
is ServerLoginResponseVerificationCodePacketEncrypted -> onPacketReceived(packet.decrypt())
is ServerLoginResponseResendPacketEncrypted -> onPacketReceived(packet.decrypt(this.tgtgtKey!!))
is ServerLoginResponseSuccessPacketEncrypted -> onPacketReceived(packet.decrypt(this.tgtgtKey!!))
is ServerSessionKeyResponsePacketEncrypted -> onPacketReceived(packet.decrypt(this._0828_rec_decr_key))
is ServerTouchResponsePacketEncrypted -> onPacketReceived(packet.decrypt())
is ServerSKeyResponsePacketEncrypted -> onPacketReceived(packet.decrypt(this.sessionKey))
else -> throw IllegalArgumentException(packet.toString())
}
@ -328,3 +361,4 @@ private lateinit var ctx: ChannelHandlerContext
}
}*/
}

View File

@ -0,0 +1,29 @@
package net.mamoe.mirai.network.packet.client.session
import net.mamoe.mirai.network.Protocol
import net.mamoe.mirai.network.packet.PacketId
import net.mamoe.mirai.network.packet.client.*
import net.mamoe.mirai.util.ClientLoginStatus
/**
* @author Him188moe
*/
@ExperimentalUnsignedTypes
@PacketId("00 EC")
class ClientLoginStatusPacket(
private val qq: Int,
private val sessionKey: ByteArray,
private val loginStatus: ClientLoginStatus
) : ClientPacket() {
override fun encode() {
this.writeRandom(2)//part of packet id
this.writeQQ(qq)
this.writeHex(Protocol._fixVer)
this.encryptAndWrite(sessionKey) {
it.writeHex("01 00")
it.writeByte(loginStatus.id)
it.writeHex("00 01 00 01 00 04 00 00 00 00")
}
}
}

View File

@ -0,0 +1,25 @@
package net.mamoe.mirai.network.packet.client.session
import net.mamoe.mirai.network.Protocol
import net.mamoe.mirai.network.packet.PacketId
import net.mamoe.mirai.network.packet.client.*
/**
* @author Him188moe
*/
@ExperimentalUnsignedTypes
@PacketId("00 1D")
class ClientSKeyRequestPacket(
private val qq: Int,
private val sessionKey: ByteArray
) : ClientPacket() {
override fun encode() {
this.writeRandom(2)//part of packet id
this.writeQQ(qq)
this.writeHex(Protocol._fixVer)
this.encryptAndWrite(sessionKey) {
it.writeHex("33 00 05 00 08 74 2E 71 71 2E 63 6F 6D 00 0A 71 75 6E 2E 71 71 2E 63 6F 6D 00 0C 71 7A 6F 6E 65 2E 71 71 2E 63 6F 6D 00 0C 6A 75 62 61 6F 2E 71 71 2E 63 6F 6D 00 09 6B 65 2E 71 71 2E 63 6F 6D")
}
}
}

View File

@ -0,0 +1,34 @@
package net.mamoe.mirai.network.packet.client.session
import net.mamoe.mirai.network.Protocol
import net.mamoe.mirai.network.packet.PacketId
import net.mamoe.mirai.network.packet.client.*
import net.mamoe.mirai.network.packet.server.ServerPacket
import java.io.DataInputStream
/**
* @author Him188moe
*/
@ExperimentalUnsignedTypes
@PacketId("00 5C")
class ClientHandshake1Packet(
val qq: Int,
val sessionKey: ByteArray
) : ClientPacket() {
override fun encode() {
this.writeRandom(2)//part of packet id
this.writeQQ(qq)
this.writeHex(Protocol._fixVer)
this.encryptAndWrite(sessionKey) {
it.writeByte(0x88)
it.writeQQ(qq)
it.writeByte(0x00)
}
}
}
class ServerHandshake1ResponsePacket(input: DataInputStream) : ServerPacket(input) {
override fun decode() {
}
}

View File

@ -2,27 +2,25 @@ package net.mamoe.mirai.network.packet.client.touch
import net.mamoe.mirai.network.Protocol
import net.mamoe.mirai.network.packet.PacketId
import net.mamoe.mirai.network.packet.client.ClientPacket
import net.mamoe.mirai.network.packet.client.writeHex
import net.mamoe.mirai.network.packet.client.writeQQ
import net.mamoe.mirai.network.packet.client.writeRandom
import net.mamoe.mirai.util.TEACryptor
import net.mamoe.mirai.network.packet.client.*
import java.io.IOException
/**
* @author Him188moe
*/
@ExperimentalUnsignedTypes
@PacketId("00 58")//todo check
class ClientHeartbeatPacket : ClientPacket() {
var qq: Int = 0
var sessionKey: ByteArray? = null//登录后获得
@PacketId("00 58")
class ClientHeartbeatPacket(
private val qq: Int,
private val sessionKey: ByteArray
) : ClientPacket() {
@Throws(IOException::class)
override fun encode() {
this.writeRandom(2)
this.writeQQ(qq)
this.writeHex(Protocol.fixVer)
this.write(TEACryptor.encrypt(byteArrayOf(0x00, 0x01, 0x00, 0x01), sessionKey))
this.encryptAndWrite(sessionKey) {
it.writeHex("00 01 00 01")
}
}
}

View File

@ -3,6 +3,9 @@ package net.mamoe.mirai.network.packet.server
import net.mamoe.mirai.network.packet.Packet
import net.mamoe.mirai.network.packet.client.toHexString
import net.mamoe.mirai.network.packet.server.login.*
import net.mamoe.mirai.network.packet.server.security.ServerLoginSuccessPacket
import net.mamoe.mirai.network.packet.server.security.ServerSKeyResponsePacketEncrypted
import net.mamoe.mirai.network.packet.server.security.ServerSessionKeyResponsePacketEncrypted
import net.mamoe.mirai.network.packet.server.touch.ServerTouchResponsePacket
import net.mamoe.mirai.network.packet.server.touch.ServerTouchResponsePacketEncrypted
import net.mamoe.mirai.util.getAllDeclaredFields
@ -28,7 +31,9 @@ abstract class ServerPacket(val input: DataInputStream) : Packet {
return when (val idHex = stream.readInt().toHexString(" ")) {
"08 25 31 01" -> ServerTouchResponsePacketEncrypted(ServerTouchResponsePacket.Type.TYPE_08_25_31_01, stream)
"08 25 31 02" -> ServerTouchResponsePacketEncrypted(ServerTouchResponsePacket.Type.TYPE_08_25_31_02, stream)
"08 36 31 03", "08 36 31 04", "08 36 31 05", "08 36 31 06" -> {
when (bytes.size) {
271, 207 -> return ServerLoginResponseResendPacketEncrypted(stream, when (idHex) {
@ -37,7 +42,7 @@ abstract class ServerPacket(val input: DataInputStream) : Packet {
println("flag=$idHex"); ServerLoginResponseResendPacket.Flag.OTHER
}
})
871 -> return ServerLoginResponseVerificationCodePacket(stream, bytes.size)
871 -> return ServerLoginResponseVerificationCodePacketEncrypted(stream)
}
if (bytes.size > 700) {
@ -53,14 +58,23 @@ abstract class ServerPacket(val input: DataInputStream) : Packet {
359 -> ServerLoginResponseFailedPacket.State.TAKEN_BACK
//unknown
63 -> throw IllegalArgumentException(bytes.size.toString())//可能是已经完成登录, 服务器拒绝第二次登录
351 -> throw IllegalArgumentException(bytes.size.toString())
63 -> throw IllegalArgumentException(bytes.size.toString() + " (Already logged in)")//可能是已经完成登录, 服务器拒绝第二次登录
351 -> throw IllegalArgumentException(bytes.size.toString() + " (Illegal package data)")//包数据有误
else -> throw IllegalArgumentException(bytes.size.toString())
}, stream)
}
else -> throw IllegalArgumentException(idHex)
"08 28 04 34" -> ServerSessionKeyResponsePacketEncrypted(stream)
else -> when (idHex.substring(0, 2)) {
"00 EC" -> ServerLoginSuccessPacket(stream)
"00 1D" -> ServerSKeyResponsePacketEncrypted(stream)
// "00 5C" ->
else -> throw IllegalArgumentException(idHex)
}
}
}
}
@ -79,10 +93,6 @@ abstract class ServerPacket(val input: DataInputStream) : Packet {
}
fun DataInputStream.skipUntil(byte: Byte) {
while (readByte() != byte);
}
fun DataInputStream.readUntil(byte: Byte): ByteArray {
var buff = byteArrayOf()
var b: Byte

View File

@ -4,7 +4,6 @@ import net.mamoe.mirai.network.packet.server.ServerPacket
import net.mamoe.mirai.network.packet.server.dataInputStream
import net.mamoe.mirai.network.packet.server.goto
import net.mamoe.mirai.util.TEACryptor
import net.mamoe.mirai.util.toUHexString
import java.io.DataInputStream
/**
@ -30,8 +29,7 @@ class ServerLoginResponseVerificationCodePacket(input: DataInputStream, val pack
this.input.skip(1)
val b = this.input.readByte()
println(b.toUHexString())
this.unknownBoolean = this.input.readByte().toInt() == 1
this.token00BA = this.input.goto(packetLength - 60).readNBytes(40)
}

View File

@ -0,0 +1,13 @@
package net.mamoe.mirai.network.packet.server.security
import net.mamoe.mirai.network.packet.server.ServerPacket
import java.io.DataInputStream
/**
* @author Him188moe
*/
class ServerLoginSuccessPacket(input: DataInputStream) : ServerPacket(input) {
override fun decode() {
}
}

View File

@ -0,0 +1,35 @@
package net.mamoe.mirai.network.packet.server.security
import net.mamoe.mirai.network.packet.server.ServerPacket
import net.mamoe.mirai.network.packet.server.dataInputStream
import net.mamoe.mirai.network.packet.server.goto
import net.mamoe.mirai.util.TEACryptor
import java.io.DataInputStream
/**
* @author Him188moe
*/
class ServerSKeyResponsePacket(input: DataInputStream) : ServerPacket(input) {
lateinit var sKey: String
override fun decode() {
this.sKey = String(this.input.goto(4).readNBytes(10))
}
}
/**
* Encrypted using [0828_rec_decr_key], decrypting in RobotNetworkHandler
*
* @author Him188moe
*/
class ServerSKeyResponsePacketEncrypted(inputStream: DataInputStream) : ServerPacket(inputStream) {
override fun decode() {
}
fun decrypt(sessionKey: ByteArray): ServerSKeyResponsePacket {
this.input goto 14
val data = this.input.readAllBytes().let { it.copyOfRange(0, it.size - 1) }
return ServerSKeyResponsePacket(TEACryptor.decrypt(data, sessionKey).dataInputStream());
}
}

View File

@ -0,0 +1,71 @@
package net.mamoe.mirai.network.packet.server.security
import net.mamoe.mirai.network.packet.client.writeHex
import net.mamoe.mirai.network.packet.server.ServerPacket
import net.mamoe.mirai.network.packet.server.dataInputStream
import net.mamoe.mirai.network.packet.server.goto
import net.mamoe.mirai.util.TEACryptor
import net.mamoe.mirai.util.lazyEncode
import java.io.DataInputStream
/**
* Dispose_0828
*
* @author Him188moe
*/
class ServerSessionKeyResponsePacket(inputStream: DataInputStream, val dataLength: Int) : ServerPacket(inputStream) {
lateinit var sessionKey: ByteArray
lateinit var tlv0105: ByteArray
@ExperimentalUnsignedTypes
override fun decode() {
when (dataLength) {
407 -> {
input goto 25
sessionKey = input.readNBytes(16)
}
439 -> {
input.goto(63)
sessionKey = input.readNBytes(16)
}
512,
527 -> {
input.goto(63)
sessionKey = input.readNBytes(16)
tlv0105 = lazyEncode {
it.writeHex("01 05 00 88 00 01 01 02 00 40 02 01 03 3C 01 03 00 00")
input.goto(dataLength - 122)
it.write(input.readNBytes(56))
it.writeHex("00 40 02 02 03 3C 01 03 00 00")
input.goto(dataLength - 55)
it.write(input.readNBytes(56))
} //todo 这个 tlv0105似乎可以保存起来然后下次登录时使用.
}
else -> throw IllegalArgumentException(dataLength.toString())
}
//tlv0105 = "01 05 00 88 00 01 01 02 00 40 02 01 03 3C 01 03 00 00" + 取文本中间(data, 取文本长度(data) 367, 167) “00 40 02 02 03 3C 01 03 00 00 ” 取文本中间 (data, 取文本长度 (data) 166, 167)
}
}
/**
* Encrypted using [0828_rec_decr_key], decrypting in RobotNetworkHandler
*
* @author Him188moe
*/
class ServerSessionKeyResponsePacketEncrypted(inputStream: DataInputStream) : ServerPacket(inputStream) {
override fun decode() {
}
fun decrypt(_0828_rec_decr_key: ByteArray): ServerSessionKeyResponsePacket {
this.input goto 14
val data = this.input.readAllBytes().let { it.copyOfRange(0, it.size - 1) }
return ServerSessionKeyResponsePacket(TEACryptor.decrypt(data, _0828_rec_decr_key).dataInputStream(), data.size);
}
}

View File

@ -1,54 +0,0 @@
package net.mamoe.mirai.network.packet.server.security
import net.mamoe.mirai.network.packet.server.ServerPacket
import net.mamoe.mirai.network.packet.server.dataInputStream
import net.mamoe.mirai.util.TEACryptor
import java.io.DataInputStream
/**
* Dispose_0828
*
* @author Him188moe
*/
class ServerSessionKeyResponsePacket(inputStream: DataInputStream) : ServerPacket(inputStream) {
lateinit var sessionKey: ByteArray
lateinit var tlv0105: ByteArray
override fun decode() {
var data = this.input.readAllBytes();
val input = data.dataInputStream()
sessionKey = when (data.size) {
407 -> {
input.skip(25)
input.readNBytes(16)
}
439, 527 -> {
input.skip(63)
input.readNBytes(16)
}
else -> throw IllegalStateException()
}
//tlv0105 = "01 05 00 88 00 01 01 02 00 40 02 01 03 3C 01 03 00 00" + 取文本中间(data, 取文本长度(data) 367, 167) “00 40 02 02 03 3C 01 03 00 00 ” 取文本中间 (data, 取文本长度 (data) 166, 167)
}
}
/**
* Encrypted using []0828_rec_decr_key], decrypting in RobotNetworkHandler
*
* @author Him188moe
*/
class ServerSessionKeyResponsePacketEncrypted(inputStream: DataInputStream) : ServerPacket(inputStream) {
override fun decode() {
}
fun decrypt(_0828_rec_decr_key: ByteArray): ServerSessionKeyResponsePacket {//todo test
this.input.skip(7)
return ServerSessionKeyResponsePacket(TEACryptor.decrypt(this.input.readAllBytes().let { it.copyOfRange(0, it.size - 1) }, _0828_rec_decr_key).dataInputStream());
//TeaDecrypt(取文本中间(data, 43, 取文本长度(data) 45), m_0828_rec_decr_key)
}
}

View File

@ -1,7 +1,6 @@
package net.mamoe.mirai.task;
import net.mamoe.mirai.MiraiServer;
import net.mamoe.mirai.event.MiraiEventHook;
import net.mamoe.mirai.event.events.server.ServerDisableEvent;
@ -90,26 +89,26 @@ public class MiraiTaskManager {
定时任务
*/
public void repeatingTask(Runnable runnable, long interval){
this.repeatingTask(runnable,interval, MiraiTaskExceptionHandler.byDefault());
public void repeatingTask(Runnable runnable, long intervalMillis) {
this.repeatingTask(runnable, intervalMillis, MiraiTaskExceptionHandler.byDefault());
}
public void repeatingTask(Runnable runnable, long interval, MiraiTaskExceptionHandler handler){
this.repeatingTask(runnable,interval,a -> true,handler);
public void repeatingTask(Runnable runnable, long intervalMillis, MiraiTaskExceptionHandler handler) {
this.repeatingTask(runnable, intervalMillis, a -> true, handler);
}
public void repeatingTask(Runnable runnable, long interval, int times){
this.repeatingTask(runnable,interval,times, MiraiTaskExceptionHandler.byDefault());
public void repeatingTask(Runnable runnable, long intervalMillis, int times) {
this.repeatingTask(runnable, intervalMillis, times, MiraiTaskExceptionHandler.byDefault());
}
public void repeatingTask(Runnable runnable, long interval, int times, MiraiTaskExceptionHandler handler){
public void repeatingTask(Runnable runnable, long intervalMillis, int times, MiraiTaskExceptionHandler handler) {
AtomicInteger integer = new AtomicInteger(times-1);
this.repeatingTask(
runnable,interval, a -> integer.getAndDecrement() > 0, handler
runnable, intervalMillis, a -> integer.getAndDecrement() > 0, handler
);
}
public <D extends Runnable> void repeatingTask(D runnable, long interval, Predicate<D> shouldContinue, MiraiTaskExceptionHandler handler){
public <D extends Runnable> void repeatingTask(D runnable, long intervalMillis, Predicate<D> shouldContinue, MiraiTaskExceptionHandler handler) {
new Thread(() -> {
do {
this.pool.execute(() -> {
@ -120,7 +119,7 @@ public class MiraiTaskManager {
}
});
try {
Thread.sleep(interval);
Thread.sleep(intervalMillis);
} catch (InterruptedException e) {
e.printStackTrace();
}
@ -128,10 +127,10 @@ public class MiraiTaskManager {
}).start();
}
public void deletingTask(Runnable runnable, long interval){
public void deletingTask(Runnable runnable, long intervalMillis) {
new Thread(() -> {
try{
Thread.sleep(interval);
Thread.sleep(intervalMillis);
} catch (InterruptedException e) {
e.printStackTrace();
}

View File

@ -0,0 +1,15 @@
package net.mamoe.mirai.util;
/**
* @author Him188moe
*/
public enum ClientLoginStatus {
ONLINE(0x0A);
// TODO: 2019/8/31 add more
public final int id;//1byte
ClientLoginStatus(int id) {
this.id = id;
}
}

View File

@ -24,11 +24,11 @@ fun ByteArray.decryptionDebugLogging() {
}
fun ServerPacket.logging() {
DebugLogger.buff.append(this.toString())
DebugLogger.buff.append(this.toString()).append("\n")
}
@ExperimentalUnsignedTypes
fun ClientPacket.logging() {
DebugLogger.buff.append(this.toString())
DebugLogger.buff.append(this.toString()).append("\n")
}

View File

@ -3,6 +3,7 @@ package net.mamoe.mirai.util
import net.mamoe.mirai.network.Protocol
import java.io.ByteArrayOutputStream
import java.io.DataOutputStream
import java.io.File
import java.lang.reflect.Field
import java.util.*
import java.util.zip.CRC32
@ -79,6 +80,24 @@ fun getRandomKey(length: Int): ByteArray {
return bytes.toByteArray()
}
operator fun File.plus(child: String): File = File(this, child)
private const val GTK_BASE_VALUE: Int = 5381
fun getGTK(sKey: String): Int {
var value = GTK_BASE_VALUE
for (c in sKey.toCharArray()) {
value += (value shl 5) + c.toInt()
}
value = value and Int.MAX_VALUE
return value
}
fun main() {
println(getGTK("ABCDEFGEFC"))
}
fun getCrc32(key: ByteArray): Int = CRC32().let { it.update(key); it.value.toInt() }