Bonjour,
Je me suis lancé dans la création d'un bot socket afin d'améliorer mes compétences en C# :D
Tout se passe bien jusqu’à l'envoi de ce fameux paquet 4.
En effet, après avoir envoyé le paquet 4, je reçois le paquet 20 (IdentificationFailedMessage) avec comme raison : WRONG_CREDENTIALS
Je suppose que l'erreur vient de ma classe RSAManager mais cela fait plusieurs jours que je cherche en vain une solution à ce problème :(
Voici ce que j'envoi :
Cliquez pour révéler
Cliquez pour masquer
Taille : 337
00 12 01 4D 00 02 1A 01 00 01 65 05 02 00 01 01
00 02 66 72 01 31 4A 82 F9 C9 F3 AB 5C BB CA 69
EC ED E5 A5 30 6E 0E 2B 88 CD 93 57 67 98 FA D3
2D 71 17 F2 79 4B AB CE 29 7F 2F F1 D6 07 92 39
6D E7 08 3A A3 26 8E DC D9 2D 10 5F BF 58 92 D0
52 8C 97 49 C8 C1 C8 CC CB DF D2 7A 0E 6B 6D 6E
E4 28 11 6D F2 26 D3 C7 D6 D2 90 25 27 B2 DD 82
95 85 07 73 C2 93 FA 9E 6C 12 3E 3F A3 88 56 A0
54 AC 5E 02 36 8C 0D CD 26 27 4E 89 FC C5 7C 4C
BD 9F 0E 2E 9C AB C0 D1 39 78 14 58 B3 9B FB 86
58 AB 6F E8 0B 6F BE 1F 42 92 6C 88 5F 98 92 74
F3 F6 F4 3D 90 36 51 F1 03 A7 EE C1 19 C3 9D A8
1F 31 12 6F 9E 41 2A 82 F3 59 36 7D A0 45 04 42
92 C1 F8 AD 78 3E 05 CB 45 B0 4D B3 18 C9 6E CE
7D B2 E4 2D 40 00 77 31 26 8C D7 5C 09 4F 65 BF
D7 5D B4 60 47 52 3C 5B 5F 3A 61 0A B4 4F AC D6
B8 6F 15 37 D1 4C FC F4 50 E5 D6 BE AE 3C 67 A1
19 B0 82 9A D3 1E 0C E2 A7 CE 32 8E 74 F6 DB 86
B1 DD 2D 0E BB 1D 0D 0F 48 15 CE 6B 48 56 79 7F
A7 A5 14 C2 E4 4F 5D E3 8A C6 92 91 4B B1 B4 7A
DA 0E 38 E4 15 51 BE 00 00 00 00 00 00 00 00 00
00
Et voici ce que Dofus envoi :
Cliquez pour révéler
Cliquez pour masquer
Taille : 288
00 12 01 1C 01 02 1A 01 00 01 65 05 02 00 01 01
00 02 66 72 01 00 34 E6 55 51 44 E8 72 DA FF 66
61 59 0B 16 4A 71 A0 E9 12 76 43 B5 A0 CB 3F 8A
84 7B 86 67 D4 86 6F D0 8D 9D D5 4B 55 BF 57 F3
97 7F 9D 2E 7E 9D 83 8E EA 8C AE 32 1C 19 B1 19
81 A0 71 CD 40 2B 49 35 CF 70 01 06 2B A1 A7 BE
15 E3 60 1A C6 02 A5 B3 BB 29 ED FB E6 03 AC 63
EC 22 46 96 A2 84 F9 F1 CC DA E5 17 6A 62 5B 76
8E 6E C6 C4 C9 99 C1 22 81 4E 85 0D FF 7B 5F 72
82 2E 02 B0 01 AE EE 0E 6D F9 B4 BC 24 63 C3 60
F4 D7 B0 83 80 6E 5C 81 94 45 C5 BA F4 4B 67 8F
F0 B6 C5 04 8D F4 BA D6 80 2F AB 9F C7 3A 25 C0
A7 BB D9 17 3B 1A 6C 9E B2 ED 49 E3 99 34 6C A3
54 3D A8 B9 6D BE 4C 54 CC E7 4C C1 DE A9 46 20
59 99 33 BE ED 25 DC 43 57 64 72 FD E1 D3 2D 80
0F 80 3A 09 F9 46 1D DB B0 11 4D AE 11 3B 42 23
C5 61 DB 04 25 3B 75 91 B6 2A 85 89 2B 73 B8 56
02 83 DB 23 25 D2 00 00 00 00 00 00 00 00 00 00
J'utilise la classe RSAManager de RebirthBot (que j'ai modifié un peu) ainsi que le reader/writer (https://cadernis.com/d/1224-nouveau-readerwriter) et le traducteur c# (https://cadernis.com/d/1081-traducteur-as-c#vb-protocolenumsgamedata) de asyade, gros merci a lui pour ses partages !
Passons au code, voici la classe RSAManager :
Cliquez pour révéler
Cliquez pour masquer
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Numerics;
using System.Security.Cryptography;
using System.Globalization;
namespace BotDofus.Crypto
{
public static class RSAManager
{
private const string _RSAPublicKey =
"MIIBUzANBgkqhkiG9w0BAQEFAAOCAUAAMIIBOwKCATIAqpzRrvO3We7EMi9cWYqdfb3rbdinTay+" +
"hxQ6t3dOiJLY4NITxyeIuy97yZYOojOlXS2SuJ4cCHjCeLCQO1FwOz+nynQWcBWecz2QdbHD2Kz7" +
"mNLd2qtZyEDO76rd7LaDOxRvgs9DsH9sfnCuKLKbd725xTLc7wRfJzOH9v9rTTYVXssXe7JUpTx8" +
"nV8yKnTiq3WpzBeZT4C3ZCR18GBBCh3NmSTbze9i2KipgZnOwBvhskVlweuqZ1KNIKsQgipBFuyw" +
"w68RGNYaAKofMVVio4amrGpCT5MM852jpHsgJJfOUHu6md1CnvdwDPbo/PKQUI0RLb0ezE5gsPma" +
"s39QBw+DiaibUkk1aCkBxTOFqpIbjfLM2/4qA6GPcWUJxP3vmGoeCTMBLNEiPfLqVm86QzUCAwEA" +
"AQ==";
public static byte[] Encrypt(List<byte> key, string Salt, string UserName, string Password)
{
RSACryptoServiceProvider RSA = GetRSA();
string NewSalt = GetSalt(Salt);
byte[] DecryptedData = PublicDecrypt(key.ToArray(), RSA.ExportParameters(false));
RSACryptoServiceProvider newRSA = new RSACryptoServiceProvider();
RSAParameters RSAKeyInfo = newRSA.ExportParameters(false);
RSAKeyInfo.Modulus = DecryptedData;
newRSA.ImportParameters(RSAKeyInfo);
List<byte> Credentials = new List<byte>();
Credentials.AddRange(Encoding.UTF8.GetBytes(NewSalt));
Credentials.Add((byte)UserName.Length);
Credentials.AddRange(Encoding.UTF8.GetBytes(UserName));
Credentials.AddRange(Encoding.UTF8.GetBytes(Password));
byte[] Encrypted = newRSA.Encrypt(Credentials.ToArray(), false);
System.Diagnostics.Debug.WriteLine("Encrypted = "+Convert.ToBase64String(Encrypted)+" - Taille : "+ Encrypted.Length +"\r\n");
return Encrypted;
}
private static RSACryptoServiceProvider GetRSA()
{
RSACryptoServiceProvider RSA = DecodeX509PublicKey(Convert.FromBase64String(_RSAPublicKey));
return RSA;
}
private static string GetSalt(string Salt)
{
if (Salt.Length < 32)
{
while (Salt.Length < 32)
{
Salt += " ";
}
}
return Salt;
}
private static byte[] PublicDecrypt(byte[] Data, RSAParameters RSAParameters)
{
BigInteger Exponent = new BigInteger(RSAParameters.Exponent.Reverse().Concat(new byte[] { 0 }).ToArray());
BigInteger Modulus = new BigInteger(RSAParameters.Modulus.Reverse().Concat(new byte[] { 0 }).ToArray());
BigInteger PreparedData = new BigInteger(Data // Our data block
.Reverse() // BigInteger has another byte order
.Concat(new byte[] { 0 }) // Append 0 so we are always handling positive numbers
.ToArray() // Constructor wants an array
);
byte[] DecryptedData = BigInteger.ModPow(PreparedData, Exponent, Modulus) // The RSA operation itself
.ToByteArray() // Make bytes from BigInteger
.Reverse() // Back to "normal" byte order
.ToArray(); // Return as byte array
return DecryptedData.SkipWhile(x => x != 0).Skip(1).ToArray(); // PKCS#1 padding
}
private static RSACryptoServiceProvider DecodeX509PublicKey(byte[] X509Key)
{
// Encoded OID sequence for PKCS#1 RSAEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
byte[] SeqOID = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
byte[] Seq = new byte[15];
// --------- Set up stream to read the ASN.1 encoded SubjectPublicKeyInfo blob ------
MemoryStream MemoryStream = new MemoryStream(X509Key);
BinaryReader BinaryReader = new BinaryReader(MemoryStream); // Wrap Memory Stream with BinaryReader for easy reading
byte Byte = 0;
ushort TwoBytes = 0;
try
{
TwoBytes = BinaryReader.ReadUInt16();
if (TwoBytes == 0x8130) // Data read as little endian order (actual data order for Sequence is 30 81)
BinaryReader.ReadByte(); // Advance 1 byte
else if (TwoBytes == 0x8230)
BinaryReader.ReadInt16(); // Advance 2 bytes
else
return null;
Seq = BinaryReader.ReadBytes(15); // Read the Sequence OID
if (!CompareByteArrays(Seq, SeqOID)) // Make sure Sequence for OID is correct
return null;
TwoBytes = BinaryReader.ReadUInt16();
if (TwoBytes == 0x8103) // Data read as little endian order (actual data order for Bit String is 03 81)
BinaryReader.ReadByte(); // Advance 1 byte
else if (TwoBytes == 0x8203)
BinaryReader.ReadInt16(); // Advance 2 bytes
else
return null;
Byte = BinaryReader.ReadByte();
if (Byte != 0x00) // Expect null byte next
return null;
TwoBytes = BinaryReader.ReadUInt16();
if (TwoBytes == 0x8130) // Data read as little endian order (actual data order for Sequence is 30 81)
BinaryReader.ReadByte(); // Advance 1 byte
else if (TwoBytes == 0x8230)
BinaryReader.ReadInt16(); // Advance 2 bytes
else
return null;
TwoBytes = BinaryReader.ReadUInt16();
byte LowByte = 0x00;
byte HighByte = 0x00;
if (TwoBytes == 0x8102) // Data read as little endian order (actual data order for Integer is 02 81)
{
LowByte = BinaryReader.ReadByte(); // Read next bytes which is bytes in modulus
}
else if (TwoBytes == 0x8202)
{
HighByte = BinaryReader.ReadByte(); // Advance 2 bytes
LowByte = BinaryReader.ReadByte();
}
else
{
return null;
}
byte[] ModInt = { LowByte, HighByte, 0x00, 0x00 }; // Reverse byte order since ASN.1 key uses big endian order
int ModSize = BitConverter.ToInt32(ModInt, 0);
byte FirstByte = BinaryReader.ReadByte();
BinaryReader.BaseStream.Seek(-1, SeekOrigin.Current);
if (FirstByte == 0x00) // If first byte (highest order) of modulus is zero, don't include it
{
BinaryReader.ReadByte(); // Skip this null byte
ModSize -= 1; // Reduce modulus buffer size by 1
}
byte[] Modulus = BinaryReader.ReadBytes(ModSize); // Read the modulus bytes
if (BinaryReader.ReadByte() != 0x02) // Expect an Integer for the exponent data
return null;
int ExponentBytes = (int)BinaryReader.ReadByte(); // Should only need one byte for actual exponent data (for all useful values)
byte[] Exponent = BinaryReader.ReadBytes(ExponentBytes);
// ------- Create RSACryptoServiceProvider instance and initialize with public key -----
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
RSAParameters RSAKeyInfo = new RSAParameters()
{
Modulus = Modulus,
Exponent = Exponent
};
RSA.ImportParameters(RSAKeyInfo);
return RSA;
}
catch (Exception)
{
return null;
}
finally
{
BinaryReader.Close();
}
}
private static bool CompareByteArrays(byte[] FirstArray, byte[] SecondArray)
{
if (FirstArray.Length != SecondArray.Length)
return false;
int i = 0;
foreach (byte Byte in FirstArray)
{
if (Byte != SecondArray)
return false;
i++;
}
return true;
}
}
}
Et voici comment je construit mon paquet :
Cliquez pour révéler
Cliquez pour masquer
// [Reception] HelloConnectMessage
Protocol.Messages.Connection.HelloConnectMessage HelloConnectMessage = new BotDofus.Protocol.Messages.Connection.HelloConnectMessage();
HelloConnectMessage.Deserialize(reader);
Log("Taille de la clé : "+ HelloConnectMessage.m_key.Count +"\r\n");
// [Envoi] IdentificationMessage
Protocol.Messages.Connection.IdentificationMessage IdentificationMessage = new BotDofus.Protocol.Messages.Connection.IdentificationMessage();
byte[] credentials = Crypto.RSAManager.Encrypt(HelloConnectMessage.m_key, HelloConnectMessage.m_salt, textBoxAccount.Text, textBoxPassword.Text);
Protocol.Types.Version.VersionExtended VersionExtended = new Protocol.Types.Version.VersionExtended();
VersionExtended.Init(2, 26, 1, 91397, 2, 0, 1, 1);
IdentificationMessage.Init(true, false, false, VersionExtended, "fr", credentials, 0, 0);
Common.IO.CustomDataWriter writer = new Common.IO.CustomDataWriter();
IdentificationMessage.Serialize(writer);
Console.WriteLine(writer.Data);
_Socket.Send(writer.Pack(IdentificationMessage.ProtocolId, writer.Data));
Et voici les paquets traduits en c# :
HelloConnectMessage
Cliquez pour révéler
Cliquez pour masquer
using System.Collections.Generic;
using BotDofus.Protocol.Messages;
using BotDofus.Protocol;
using BotDofus.Common.IO;
namespace BotDofus.Protocol.Messages.Connection
{
public class HelloConnectMessage
{
public const int Id = 3;
public string m_salt;
public List<System.Byte> m_key;
public void Init(string salt, List<System.Byte> key)
{
m_salt = salt;
m_key = key;
}
public void Serialize(IDataWriter writer)
{
writer.WriteUTF(m_salt);
writer.WriteShort(((short)(m_key.Count)));
int keyIndex;
for (keyIndex = 0; (keyIndex < m_key.Count); keyIndex = (keyIndex + 1))
{
writer.WriteByte(m_key[keyIndex]);
}
}
public void Deserialize(IDataReader reader)
{
m_salt = reader.ReadUTF();
int keyCount = reader.ReadUShort();
int keyIndex;
m_key = new System.Collections.Generic.List<byte>();
for (keyIndex = 0; (keyIndex < keyCount); keyIndex = (keyIndex + 1))
{
m_key.Add(reader.ReadByte());
}
}
}
}
IdentificationMessage
Cliquez pour révéler
Cliquez pour masquer
using System.Collections.Generic;
using BotDofus.Protocol.Messages;
using BotDofus.Protocol.Types;
using BotDofus.Protocol.Types.Version;
using BotDofus.Protocol;
using BotDofus.Common.IO;
namespace BotDofus.Protocol.Messages.Connection
{
public class IdentificationMessage
{
public const int Id = 4;
public bool m_autoconnect;
public bool m_useCertificate;
public bool m_useLoginToken;
public VersionExtended m_version;
public string m_lang;
public byte[] m_credentials;
public short m_serverId;
public double m_sessionOptionalSalt;
public void Init(bool autoconnect, bool useCertificate, bool useLoginToken, VersionExtended version, string lang, byte[] credentials, short serverId, double sessionOptionalSalt)
{
m_autoconnect = autoconnect;
m_useCertificate = useCertificate;
m_useLoginToken = useLoginToken;
m_version = version;
m_lang = lang;
m_credentials = credentials;
m_serverId = serverId;
m_sessionOptionalSalt = sessionOptionalSalt;
}
public void Serialize(IDataWriter writer)
{
byte flag = new byte();
BooleanByteWrapper.SetFlag(0, flag, m_autoconnect);
BooleanByteWrapper.SetFlag(1, flag, m_useCertificate);
BooleanByteWrapper.SetFlag(2, flag, m_useLoginToken);
writer.WriteByte(flag);
m_version.Serialize(writer);
writer.WriteUTF(m_lang);
writer.WriteShort(((short)(m_credentials.Length)));
int credentialsIndex;
for (credentialsIndex = 0; (credentialsIndex < m_credentials.Length); credentialsIndex = (credentialsIndex + 1))
{
writer.WriteByte(m_credentials[credentialsIndex]);
}
writer.WriteShort(m_serverId);
writer.WriteDouble(m_sessionOptionalSalt);
}
public void Deserialize(IDataReader reader)
{
byte flag = reader.ReadByte();
m_autoconnect = BooleanByteWrapper.GetFlag(flag, 0);
m_useCertificate = BooleanByteWrapper.GetFlag(flag, 1);
m_useLoginToken = BooleanByteWrapper.GetFlag(flag, 2);
m_version = new VersionExtended();
m_version.Deserialize(reader);
m_lang = reader.ReadUTF();
int credentialsCount = reader.ReadShort();
int credentialsIndex;
m_credentials = new byte[credentialsCount];
for (credentialsIndex = 0; (credentialsIndex < credentialsCount); credentialsIndex = (credentialsIndex + 1))
{
m_credentials[credentialsIndex] = reader.ReadByte();
}
m_serverId = reader.ReadShort();
m_sessionOptionalSalt = reader.ReadDouble();
}
}
}
Je sais que le code n'est pas très optimisé mais je voulais d'abord finir la connexion puis après optimiser le code.
Si vous avez des pistes je suis preneur !