From 24b75e45c7dbfb909aceca0dcc4b28cf8301b82b Mon Sep 17 00:00:00 2001 From: wdvxdr Date: Thu, 23 Dec 2021 17:04:37 +0800 Subject: [PATCH] internal/oicq: refactor & support unmarshal --- client/builders.go | 16 ++--- client/client.go | 13 ++-- client/internal/auth/auth.go | 21 +++--- client/internal/codec/oicq.go | 40 ----------- client/internal/oicq/oicq.go | 130 ++++++++++++++++++++++++++++++++++ client/network.go | 3 +- client/packet.go | 15 ++-- client/tlv_decoders.go | 3 +- internal/crypto/crypto.go | 66 ++++------------- 9 files changed, 175 insertions(+), 132 deletions(-) delete mode 100644 client/internal/codec/oicq.go create mode 100644 client/internal/oicq/oicq.go diff --git a/client/builders.go b/client/builders.go index 407022cd..8e08165f 100644 --- a/client/builders.go +++ b/client/builders.go @@ -9,15 +9,14 @@ import ( "github.com/Mrs4s/MiraiGo/binary" "github.com/Mrs4s/MiraiGo/binary/jce" "github.com/Mrs4s/MiraiGo/client/internal/auth" - "github.com/Mrs4s/MiraiGo/client/internal/codec" "github.com/Mrs4s/MiraiGo/client/internal/network" + "github.com/Mrs4s/MiraiGo/client/internal/oicq" "github.com/Mrs4s/MiraiGo/client/pb" "github.com/Mrs4s/MiraiGo/client/pb/cmd0x352" "github.com/Mrs4s/MiraiGo/client/pb/msg" "github.com/Mrs4s/MiraiGo/client/pb/oidb" "github.com/Mrs4s/MiraiGo/client/pb/profilecard" "github.com/Mrs4s/MiraiGo/client/pb/structmsg" - "github.com/Mrs4s/MiraiGo/internal/crypto" "github.com/Mrs4s/MiraiGo/internal/packets" "github.com/Mrs4s/MiraiGo/internal/proto" "github.com/Mrs4s/MiraiGo/internal/tlv" @@ -426,12 +425,11 @@ func (c *QQClient) buildRequestTgtgtNopicsigPacket() (uint16, []byte) { w.Write(tlv.T545([]byte(c.deviceInfo.IMEI))) }) - oicq := codec.OICQ{ - Uin: uint32(c.Uin), - Command: 0x810, - EncryptMethod: crypto.NewEncryptSession(c.sig.T133), - Key: c.sig.WtSessionTicketKey, - Body: req, + m := oicq.Message{ + Uin: uint32(c.Uin), + Command: 0x810, + EncryptionMethod: oicq.EM_ST, + Body: req, } nreq := network.Request{ @@ -440,7 +438,7 @@ func (c *QQClient) buildRequestTgtgtNopicsigPacket() (uint16, []byte) { Uin: c.Uin, SequenceID: int32(seq), CommandName: "wtlogin.exchange_emp", - Body: oicq.Encode(), + Body: c.oicq.Marshal(&m), } return seq, c.transport.PackPacket(&nreq) } diff --git a/client/client.go b/client/client.go index 1ef81104..ef9b0391 100644 --- a/client/client.go +++ b/client/client.go @@ -18,8 +18,8 @@ import ( "github.com/Mrs4s/MiraiGo/client/internal/auth" "github.com/Mrs4s/MiraiGo/client/internal/highway" "github.com/Mrs4s/MiraiGo/client/internal/network" + "github.com/Mrs4s/MiraiGo/client/internal/oicq" "github.com/Mrs4s/MiraiGo/client/pb/msg" - "github.com/Mrs4s/MiraiGo/internal/crypto" "github.com/Mrs4s/MiraiGo/utils" ) @@ -54,6 +54,7 @@ type QQClient struct { ConnectTime time.Time transport *network.Transport + oicq *oicq.Codec // internal state handlers HandlerMap @@ -64,7 +65,6 @@ type QQClient struct { version *auth.AppVersion deviceInfo *auth.Device alive bool - ecdh *crypto.EncryptECDH // session info qwebSeq atomic.Int64 @@ -161,7 +161,6 @@ func NewClientMd5(uin int64, passwordMd5 [16]byte) *QQClient { Uin: uin, PasswordMd5: passwordMd5, AllowSlider: true, - RandomKey: make([]byte, 16), TCP: &network.TCPListener{}, sig: &auth.SigInfo{ OutPacketSessionID: []byte{0x02, 0xB0, 0x5B, 0x8B}, @@ -172,7 +171,6 @@ func NewClientMd5(uin int64, passwordMd5 [16]byte) *QQClient { onlinePushCache: utils.NewCache(time.Second * 15), servers: []*net.TCPAddr{}, alive: true, - ecdh: crypto.NewEcdh(), highwaySession: new(highway.Session), version: new(auth.AppVersion), @@ -184,7 +182,7 @@ func NewClientMd5(uin int64, passwordMd5 [16]byte) *QQClient { Version: cli.version, Device: cli.deviceInfo, } - + cli.oicq = oicq.NewCodec(cli.Uin) { // init atomic values cli.SequenceId.Store(0x3635) cli.requestPacketRequestID.Store(1921334513) @@ -194,7 +192,6 @@ func NewClientMd5(uin int64, passwordMd5 [16]byte) *QQClient { } cli.highwaySession.Uin = strconv.FormatInt(cli.Uin, 10) cli.GuildService = &GuildService{c: cli} - cli.ecdh.FetchPubKey(uin) cli.UseDevice(SystemDeviceInfo) sso, err := getSSOAddress() if err == nil && len(sso) > 0 { @@ -300,7 +297,7 @@ func (c *QQClient) TokenLogin(token []byte) error { c.sig.SrmToken = r.ReadBytesShort() c.sig.T133 = r.ReadBytesShort() c.sig.EncryptedA1 = r.ReadBytesShort() - c.sig.WtSessionTicketKey = r.ReadBytesShort() + c.oicq.WtSessionTicketKey = r.ReadBytesShort() c.sig.OutPacketSessionID = r.ReadBytesShort() // SystemDeviceInfo.TgtgtKey = r.ReadBytesShort() c.deviceInfo.TgtgtKey = r.ReadBytesShort() @@ -448,7 +445,7 @@ func (c *QQClient) GenToken() []byte { w.WriteBytesShort(c.sig.SrmToken) w.WriteBytesShort(c.sig.T133) w.WriteBytesShort(c.sig.EncryptedA1) - w.WriteBytesShort(c.sig.WtSessionTicketKey) + w.WriteBytesShort(c.oicq.WtSessionTicketKey) w.WriteBytesShort(c.sig.OutPacketSessionID) w.WriteBytesShort(c.deviceInfo.TgtgtKey) }) diff --git a/client/internal/auth/auth.go b/client/internal/auth/auth.go index 2afc9746..0ae462bb 100644 --- a/client/internal/auth/auth.go +++ b/client/internal/auth/auth.go @@ -113,17 +113,16 @@ type SigInfo struct { TGT []byte TGTKey []byte - SrmToken []byte // study room manager | 0x16a - T133 []byte - EncryptedA1 []byte - UserStKey []byte - UserStWebSig []byte - SKey []byte - SKeyExpiredTime int64 - D2 []byte - D2Key []byte - WtSessionTicketKey []byte - DeviceToken []byte + SrmToken []byte // study room manager | 0x16a + T133 []byte + EncryptedA1 []byte + UserStKey []byte + UserStWebSig []byte + SKey []byte + SKeyExpiredTime int64 + D2 []byte + D2Key []byte + DeviceToken []byte PsKeyMap map[string][]byte Pt4TokenMap map[string][]byte diff --git a/client/internal/codec/oicq.go b/client/internal/codec/oicq.go deleted file mode 100644 index 74d86404..00000000 --- a/client/internal/codec/oicq.go +++ /dev/null @@ -1,40 +0,0 @@ -package codec - -import "github.com/Mrs4s/MiraiGo/binary" - -type Encryptor interface { - Encrypt([]byte, []byte) []byte - ID() byte -} - -type OICQ struct { - Uin uint32 - Command uint16 - EncryptMethod Encryptor - Key []byte - Body []byte -} - -func (m *OICQ) Encode() []byte { - body := m.EncryptMethod.Encrypt(m.Body, m.Key) - - p := binary.SelectWriter() - defer binary.PutWriter(p) - - p.WriteByte(0x02) - p.WriteUInt16(27 + 2 + uint16(len(body))) - p.WriteUInt16(8001) - p.WriteUInt16(m.Command) - p.WriteUInt16(1) - p.WriteUInt32(m.Uin) - p.WriteByte(3) - p.WriteByte(m.EncryptMethod.ID()) - p.WriteByte(0) - p.WriteUInt32(2) - p.WriteUInt32(0) - p.WriteUInt32(0) - p.Write(body) - p.WriteByte(0x03) - - return append([]byte(nil), p.Bytes()...) -} diff --git a/client/internal/oicq/oicq.go b/client/internal/oicq/oicq.go new file mode 100644 index 00000000..c4469a40 --- /dev/null +++ b/client/internal/oicq/oicq.go @@ -0,0 +1,130 @@ +package oicq + +import ( + goBinary "encoding/binary" + "math/rand" + + "github.com/pkg/errors" + + "github.com/Mrs4s/MiraiGo/binary" + "github.com/Mrs4s/MiraiGo/internal/crypto" +) + +type Codec struct { + ecdh *crypto.ECDH + randomKey []byte + + WtSessionTicketKey []byte +} + +func NewCodec(uin int64) *Codec { + c := &Codec{ + ecdh: crypto.NewECDH(), + randomKey: make([]byte, 16), + } + rand.Read(c.randomKey) + c.ecdh.FetchPubKey(uin) + return c +} + +type EncryptionMethod byte + +const ( + EM_ECDH EncryptionMethod = iota + EM_ST +) + +type Message struct { + Uin uint32 + Command uint16 + EncryptionMethod EncryptionMethod + Body []byte +} + +func (c *Codec) Marshal(m *Message) []byte { + w := binary.SelectWriter() + defer binary.PutWriter(w) + + w.WriteByte(0x02) + w.WriteUInt16(0) // len 占位 + w.WriteUInt16(8001) // version? + w.WriteUInt16(m.Command) + w.WriteUInt16(1) + w.WriteUInt32(m.Uin) + w.WriteByte(0x03) + switch m.EncryptionMethod { + case EM_ECDH: + w.WriteByte(0x87) + case EM_ST: + w.WriteByte(0x45) + } + w.WriteByte(0) + w.WriteUInt32(2) + w.WriteUInt32(0) + w.WriteUInt32(0) + + switch m.EncryptionMethod { + case EM_ECDH: + w.WriteByte(0x02) + w.WriteByte(0x01) + w.Write(c.randomKey) + w.WriteUInt16(0x01_31) + w.WriteUInt16(c.ecdh.SvrPublicKeyVer) + w.WriteUInt16(uint16(len(c.ecdh.PublicKey))) + w.Write(c.ecdh.PublicKey) + w.EncryptAndWrite(c.ecdh.ShareKey, m.Body) + + case EM_ST: + w.WriteByte(0x01) + w.WriteByte(0x03) + w.Write(c.randomKey) + w.WriteUInt16(0x0102) + w.WriteUInt16(0x0000) + w.EncryptAndWrite(c.randomKey, m.Body) + } + w.WriteByte(0x03) + + buf := append([]byte(nil), w.Bytes()...) + goBinary.BigEndian.PutUint16(buf[1:3], uint16(len(buf))) + return buf +} + +var ( + ErrUnknownFlag = errors.New("unknown flag") + ErrUnknownEncryptType = errors.New("unknown encrypt type") +) + +func (c *Codec) Unmarshal(data []byte) (*Message, error) { + reader := binary.NewReader(data) + if flag := reader.ReadByte(); flag != 2 { + return nil, ErrUnknownFlag + } + m := new(Message) + reader.ReadUInt16() // len + reader.ReadUInt16() // version? + m.Command = reader.ReadUInt16() + reader.ReadUInt16() // 1? + m.Uin = uint32(reader.ReadInt32()) + reader.ReadByte() + encryptType := reader.ReadByte() + reader.ReadByte() + switch encryptType { + case 0: + m.Body = func() (decrypted []byte) { + d := reader.ReadBytes(reader.Len() - 1) + defer func() { + if pan := recover(); pan != nil { + tea := binary.NewTeaCipher(c.randomKey) + decrypted = tea.Decrypt(d) + } + }() + return binary.NewTeaCipher(c.ecdh.ShareKey).Decrypt(d) + }() + case 3: + d := reader.ReadBytes(reader.Len() - 1) + m.Body = binary.NewTeaCipher(c.WtSessionTicketKey).Decrypt(d) + default: + return nil, ErrUnknownEncryptType + } + return m, nil +} diff --git a/client/network.go b/client/network.go index c94a539a..cadf4007 100644 --- a/client/network.go +++ b/client/network.go @@ -306,7 +306,7 @@ func (c *QQClient) netLoop() { continue } if pkt.Flag2 == 2 { - pkt.Payload, err = pkt.DecryptPayload(c.ecdh.InitialShareKey, c.RandomKey, c.sig.WtSessionTicketKey) + m, err := c.oicq.Unmarshal(pkt.Payload) if err != nil { c.Error("decrypt payload error: %v", err) if errors.Is(err, packets.ErrUnknownFlag) { @@ -314,6 +314,7 @@ func (c *QQClient) netLoop() { } continue } + pkt.Payload = m.Body } errCount = 0 c.Debug("rev pkt: %v seq: %v", pkt.CommandName, pkt.SequenceId) diff --git a/client/packet.go b/client/packet.go index 8261b2d8..ddf6e11a 100644 --- a/client/packet.go +++ b/client/packet.go @@ -1,20 +1,19 @@ package client import ( - "github.com/Mrs4s/MiraiGo/client/internal/codec" "github.com/Mrs4s/MiraiGo/client/internal/network" + "github.com/Mrs4s/MiraiGo/client/internal/oicq" ) //go:noinline func (c *QQClient) buildOicqRequestPacket(uin int64, command uint16, body []byte) []byte { - req := codec.OICQ{ - Uin: uint32(uin), - Command: command, - EncryptMethod: c.ecdh, - Key: c.RandomKey, - Body: body, + req := oicq.Message{ + Uin: uint32(uin), + Command: command, + EncryptionMethod: oicq.EM_ECDH, + Body: body, } - return req.Encode() + return c.oicq.Marshal(&req) } //go:noinline diff --git a/client/tlv_decoders.go b/client/tlv_decoders.go index 64112e98..a28bd9eb 100644 --- a/client/tlv_decoders.go +++ b/client/tlv_decoders.go @@ -89,6 +89,8 @@ func (c *QQClient) decodeT119(data, ek []byte) { // readT138(t138) // chg time } + c.oicq.WtSessionTicketKey = utils.Select(m[0x134], c.oicq.WtSessionTicketKey) + // we don't use `c.sigInfo = &auth.SigInfo{...}` here, // because we need keep other fields in `c.sigInfo` s := c.sig @@ -104,7 +106,6 @@ func (c *QQClient) decodeT119(data, ek []byte) { s.SKeyExpiredTime = time.Now().Unix() + 21600 s.D2 = m[0x143] s.D2Key = m[0x305] - s.WtSessionTicketKey = utils.Select(m[0x134], s.WtSessionTicketKey) s.DeviceToken = m[0x322] s.PsKeyMap = psKeyMap diff --git a/internal/crypto/crypto.go b/internal/crypto/crypto.go index 1d423a5f..48cb1d03 100644 --- a/internal/crypto/crypto.go +++ b/internal/crypto/crypto.go @@ -8,14 +8,12 @@ import ( "encoding/json" "net/http" "strconv" - - "github.com/Mrs4s/MiraiGo/binary" ) -type EncryptECDH struct { - InitialShareKey []byte +type ECDH struct { + SvrPublicKeyVer uint16 PublicKey []byte - PublicKeyVer uint16 + ShareKey []byte } type EncryptSession struct { @@ -24,65 +22,25 @@ type EncryptSession struct { const serverPublicKey = "04EBCA94D733E399B2DB96EACDD3F69A8BB0F74224E2B44E3357812211D2E62EFBC91BB553098E25E33A799ADC7F76FEB208DA7C6522CDB0719A305180CC54A82E" -func NewEcdh() *EncryptECDH { - e := &EncryptECDH{ - PublicKeyVer: 1, +func NewECDH() *ECDH { + e := &ECDH{ + SvrPublicKeyVer: 1, } e.generateKey(serverPublicKey) return e } -func (e *EncryptECDH) generateKey(sPubKey string) { - pub, err := hex.DecodeString(sPubKey) - if err != nil { - panic(err) - } +func (e *ECDH) generateKey(sPubKey string) { + pub, _ := hex.DecodeString(sPubKey) p256 := elliptic.P256() - key, sx, sy, err := elliptic.GenerateKey(p256, rand.Reader) - if err != nil { - panic(err) - } + key, sx, sy, _ := elliptic.GenerateKey(p256, rand.Reader) tx, ty := elliptic.Unmarshal(p256, pub) x, _ := p256.ScalarMult(tx, ty, key) hash := md5.Sum(x.Bytes()[:16]) - e.InitialShareKey = hash[:] + e.ShareKey = hash[:] e.PublicKey = elliptic.Marshal(p256, sx, sy) } -func (e *EncryptECDH) Encrypt(d, k []byte) []byte { - w := binary.SelectWriter() - w.WriteByte(0x02) - w.WriteByte(0x01) - w.Write(k) - w.WriteUInt16(0x01_31) - w.WriteUInt16(e.PublicKeyVer) - w.WriteUInt16(uint16(len(e.PublicKey))) - w.Write(e.PublicKey) - w.EncryptAndWrite(e.InitialShareKey, d) - return w.Bytes() -} - -func (e *EncryptECDH) ID() byte { - return 0x87 -} - -func NewEncryptSession(t133 []byte) *EncryptSession { - return &EncryptSession{T133: t133} -} - -func (e *EncryptSession) Encrypt(d, k []byte) []byte { - return binary.NewWriterF(func(w *binary.Writer) { - encrypt := binary.NewTeaCipher(k).Encrypt(d) - w.WriteUInt16(uint16(len(e.T133))) - w.Write(e.T133) - w.Write(encrypt) - }) -} - -func (e *EncryptSession) ID() byte { - return 69 -} - type pubKeyResp struct { Meta struct { PubKeyVer uint16 `json:"KeyVer"` @@ -91,7 +49,7 @@ type pubKeyResp struct { } // FetchPubKey 从服务器获取PubKey -func (e *EncryptECDH) FetchPubKey(uin int64) { +func (e *ECDH) FetchPubKey(uin int64) { resp, err := http.Get("https://keyrotate.qq.com/rotate_key?cipher_suite_ver=305&uin=" + strconv.FormatInt(uin, 10)) if err != nil { return @@ -102,6 +60,6 @@ func (e *EncryptECDH) FetchPubKey(uin int64) { if err != nil { return } - e.PublicKeyVer = pubKey.Meta.PubKeyVer + e.SvrPublicKeyVer = pubKey.Meta.PubKeyVer e.generateKey(pubKey.Meta.PubKey) // todo check key sign }