1
0
mirror of https://github.com/Mrs4s/MiraiGo.git synced 2025-05-04 11:07:40 +08:00

internal/oicq: refactor & support unmarshal

This commit is contained in:
wdvxdr 2021-12-23 17:04:37 +08:00
parent 5dfa4528dd
commit 24b75e45c7
No known key found for this signature in database
GPG Key ID: 703F8C071DE7A1B6
9 changed files with 175 additions and 132 deletions

View File

@ -9,15 +9,14 @@ import (
"github.com/Mrs4s/MiraiGo/binary" "github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/binary/jce" "github.com/Mrs4s/MiraiGo/binary/jce"
"github.com/Mrs4s/MiraiGo/client/internal/auth" "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/network"
"github.com/Mrs4s/MiraiGo/client/internal/oicq"
"github.com/Mrs4s/MiraiGo/client/pb" "github.com/Mrs4s/MiraiGo/client/pb"
"github.com/Mrs4s/MiraiGo/client/pb/cmd0x352" "github.com/Mrs4s/MiraiGo/client/pb/cmd0x352"
"github.com/Mrs4s/MiraiGo/client/pb/msg" "github.com/Mrs4s/MiraiGo/client/pb/msg"
"github.com/Mrs4s/MiraiGo/client/pb/oidb" "github.com/Mrs4s/MiraiGo/client/pb/oidb"
"github.com/Mrs4s/MiraiGo/client/pb/profilecard" "github.com/Mrs4s/MiraiGo/client/pb/profilecard"
"github.com/Mrs4s/MiraiGo/client/pb/structmsg" "github.com/Mrs4s/MiraiGo/client/pb/structmsg"
"github.com/Mrs4s/MiraiGo/internal/crypto"
"github.com/Mrs4s/MiraiGo/internal/packets" "github.com/Mrs4s/MiraiGo/internal/packets"
"github.com/Mrs4s/MiraiGo/internal/proto" "github.com/Mrs4s/MiraiGo/internal/proto"
"github.com/Mrs4s/MiraiGo/internal/tlv" "github.com/Mrs4s/MiraiGo/internal/tlv"
@ -426,11 +425,10 @@ func (c *QQClient) buildRequestTgtgtNopicsigPacket() (uint16, []byte) {
w.Write(tlv.T545([]byte(c.deviceInfo.IMEI))) w.Write(tlv.T545([]byte(c.deviceInfo.IMEI)))
}) })
oicq := codec.OICQ{ m := oicq.Message{
Uin: uint32(c.Uin), Uin: uint32(c.Uin),
Command: 0x810, Command: 0x810,
EncryptMethod: crypto.NewEncryptSession(c.sig.T133), EncryptionMethod: oicq.EM_ST,
Key: c.sig.WtSessionTicketKey,
Body: req, Body: req,
} }
@ -440,7 +438,7 @@ func (c *QQClient) buildRequestTgtgtNopicsigPacket() (uint16, []byte) {
Uin: c.Uin, Uin: c.Uin,
SequenceID: int32(seq), SequenceID: int32(seq),
CommandName: "wtlogin.exchange_emp", CommandName: "wtlogin.exchange_emp",
Body: oicq.Encode(), Body: c.oicq.Marshal(&m),
} }
return seq, c.transport.PackPacket(&nreq) return seq, c.transport.PackPacket(&nreq)
} }

View File

@ -18,8 +18,8 @@ import (
"github.com/Mrs4s/MiraiGo/client/internal/auth" "github.com/Mrs4s/MiraiGo/client/internal/auth"
"github.com/Mrs4s/MiraiGo/client/internal/highway" "github.com/Mrs4s/MiraiGo/client/internal/highway"
"github.com/Mrs4s/MiraiGo/client/internal/network" "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/client/pb/msg"
"github.com/Mrs4s/MiraiGo/internal/crypto"
"github.com/Mrs4s/MiraiGo/utils" "github.com/Mrs4s/MiraiGo/utils"
) )
@ -54,6 +54,7 @@ type QQClient struct {
ConnectTime time.Time ConnectTime time.Time
transport *network.Transport transport *network.Transport
oicq *oicq.Codec
// internal state // internal state
handlers HandlerMap handlers HandlerMap
@ -64,7 +65,6 @@ type QQClient struct {
version *auth.AppVersion version *auth.AppVersion
deviceInfo *auth.Device deviceInfo *auth.Device
alive bool alive bool
ecdh *crypto.EncryptECDH
// session info // session info
qwebSeq atomic.Int64 qwebSeq atomic.Int64
@ -161,7 +161,6 @@ func NewClientMd5(uin int64, passwordMd5 [16]byte) *QQClient {
Uin: uin, Uin: uin,
PasswordMd5: passwordMd5, PasswordMd5: passwordMd5,
AllowSlider: true, AllowSlider: true,
RandomKey: make([]byte, 16),
TCP: &network.TCPListener{}, TCP: &network.TCPListener{},
sig: &auth.SigInfo{ sig: &auth.SigInfo{
OutPacketSessionID: []byte{0x02, 0xB0, 0x5B, 0x8B}, OutPacketSessionID: []byte{0x02, 0xB0, 0x5B, 0x8B},
@ -172,7 +171,6 @@ func NewClientMd5(uin int64, passwordMd5 [16]byte) *QQClient {
onlinePushCache: utils.NewCache(time.Second * 15), onlinePushCache: utils.NewCache(time.Second * 15),
servers: []*net.TCPAddr{}, servers: []*net.TCPAddr{},
alive: true, alive: true,
ecdh: crypto.NewEcdh(),
highwaySession: new(highway.Session), highwaySession: new(highway.Session),
version: new(auth.AppVersion), version: new(auth.AppVersion),
@ -184,7 +182,7 @@ func NewClientMd5(uin int64, passwordMd5 [16]byte) *QQClient {
Version: cli.version, Version: cli.version,
Device: cli.deviceInfo, Device: cli.deviceInfo,
} }
cli.oicq = oicq.NewCodec(cli.Uin)
{ // init atomic values { // init atomic values
cli.SequenceId.Store(0x3635) cli.SequenceId.Store(0x3635)
cli.requestPacketRequestID.Store(1921334513) 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.highwaySession.Uin = strconv.FormatInt(cli.Uin, 10)
cli.GuildService = &GuildService{c: cli} cli.GuildService = &GuildService{c: cli}
cli.ecdh.FetchPubKey(uin)
cli.UseDevice(SystemDeviceInfo) cli.UseDevice(SystemDeviceInfo)
sso, err := getSSOAddress() sso, err := getSSOAddress()
if err == nil && len(sso) > 0 { if err == nil && len(sso) > 0 {
@ -300,7 +297,7 @@ func (c *QQClient) TokenLogin(token []byte) error {
c.sig.SrmToken = r.ReadBytesShort() c.sig.SrmToken = r.ReadBytesShort()
c.sig.T133 = r.ReadBytesShort() c.sig.T133 = r.ReadBytesShort()
c.sig.EncryptedA1 = r.ReadBytesShort() c.sig.EncryptedA1 = r.ReadBytesShort()
c.sig.WtSessionTicketKey = r.ReadBytesShort() c.oicq.WtSessionTicketKey = r.ReadBytesShort()
c.sig.OutPacketSessionID = r.ReadBytesShort() c.sig.OutPacketSessionID = r.ReadBytesShort()
// SystemDeviceInfo.TgtgtKey = r.ReadBytesShort() // SystemDeviceInfo.TgtgtKey = r.ReadBytesShort()
c.deviceInfo.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.SrmToken)
w.WriteBytesShort(c.sig.T133) w.WriteBytesShort(c.sig.T133)
w.WriteBytesShort(c.sig.EncryptedA1) w.WriteBytesShort(c.sig.EncryptedA1)
w.WriteBytesShort(c.sig.WtSessionTicketKey) w.WriteBytesShort(c.oicq.WtSessionTicketKey)
w.WriteBytesShort(c.sig.OutPacketSessionID) w.WriteBytesShort(c.sig.OutPacketSessionID)
w.WriteBytesShort(c.deviceInfo.TgtgtKey) w.WriteBytesShort(c.deviceInfo.TgtgtKey)
}) })

View File

@ -122,7 +122,6 @@ type SigInfo struct {
SKeyExpiredTime int64 SKeyExpiredTime int64
D2 []byte D2 []byte
D2Key []byte D2Key []byte
WtSessionTicketKey []byte
DeviceToken []byte DeviceToken []byte
PsKeyMap map[string][]byte PsKeyMap map[string][]byte

View File

@ -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()...)
}

View File

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

View File

@ -306,7 +306,7 @@ func (c *QQClient) netLoop() {
continue continue
} }
if pkt.Flag2 == 2 { 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 { if err != nil {
c.Error("decrypt payload error: %v", err) c.Error("decrypt payload error: %v", err)
if errors.Is(err, packets.ErrUnknownFlag) { if errors.Is(err, packets.ErrUnknownFlag) {
@ -314,6 +314,7 @@ func (c *QQClient) netLoop() {
} }
continue continue
} }
pkt.Payload = m.Body
} }
errCount = 0 errCount = 0
c.Debug("rev pkt: %v seq: %v", pkt.CommandName, pkt.SequenceId) c.Debug("rev pkt: %v seq: %v", pkt.CommandName, pkt.SequenceId)

View File

@ -1,20 +1,19 @@
package client package client
import ( import (
"github.com/Mrs4s/MiraiGo/client/internal/codec"
"github.com/Mrs4s/MiraiGo/client/internal/network" "github.com/Mrs4s/MiraiGo/client/internal/network"
"github.com/Mrs4s/MiraiGo/client/internal/oicq"
) )
//go:noinline //go:noinline
func (c *QQClient) buildOicqRequestPacket(uin int64, command uint16, body []byte) []byte { func (c *QQClient) buildOicqRequestPacket(uin int64, command uint16, body []byte) []byte {
req := codec.OICQ{ req := oicq.Message{
Uin: uint32(uin), Uin: uint32(uin),
Command: command, Command: command,
EncryptMethod: c.ecdh, EncryptionMethod: oicq.EM_ECDH,
Key: c.RandomKey,
Body: body, Body: body,
} }
return req.Encode() return c.oicq.Marshal(&req)
} }
//go:noinline //go:noinline

View File

@ -89,6 +89,8 @@ func (c *QQClient) decodeT119(data, ek []byte) {
// readT138(t138) // chg time // readT138(t138) // chg time
} }
c.oicq.WtSessionTicketKey = utils.Select(m[0x134], c.oicq.WtSessionTicketKey)
// we don't use `c.sigInfo = &auth.SigInfo{...}` here, // we don't use `c.sigInfo = &auth.SigInfo{...}` here,
// because we need keep other fields in `c.sigInfo` // because we need keep other fields in `c.sigInfo`
s := c.sig s := c.sig
@ -104,7 +106,6 @@ func (c *QQClient) decodeT119(data, ek []byte) {
s.SKeyExpiredTime = time.Now().Unix() + 21600 s.SKeyExpiredTime = time.Now().Unix() + 21600
s.D2 = m[0x143] s.D2 = m[0x143]
s.D2Key = m[0x305] s.D2Key = m[0x305]
s.WtSessionTicketKey = utils.Select(m[0x134], s.WtSessionTicketKey)
s.DeviceToken = m[0x322] s.DeviceToken = m[0x322]
s.PsKeyMap = psKeyMap s.PsKeyMap = psKeyMap

View File

@ -8,14 +8,12 @@ import (
"encoding/json" "encoding/json"
"net/http" "net/http"
"strconv" "strconv"
"github.com/Mrs4s/MiraiGo/binary"
) )
type EncryptECDH struct { type ECDH struct {
InitialShareKey []byte SvrPublicKeyVer uint16
PublicKey []byte PublicKey []byte
PublicKeyVer uint16 ShareKey []byte
} }
type EncryptSession struct { type EncryptSession struct {
@ -24,65 +22,25 @@ type EncryptSession struct {
const serverPublicKey = "04EBCA94D733E399B2DB96EACDD3F69A8BB0F74224E2B44E3357812211D2E62EFBC91BB553098E25E33A799ADC7F76FEB208DA7C6522CDB0719A305180CC54A82E" const serverPublicKey = "04EBCA94D733E399B2DB96EACDD3F69A8BB0F74224E2B44E3357812211D2E62EFBC91BB553098E25E33A799ADC7F76FEB208DA7C6522CDB0719A305180CC54A82E"
func NewEcdh() *EncryptECDH { func NewECDH() *ECDH {
e := &EncryptECDH{ e := &ECDH{
PublicKeyVer: 1, SvrPublicKeyVer: 1,
} }
e.generateKey(serverPublicKey) e.generateKey(serverPublicKey)
return e return e
} }
func (e *EncryptECDH) generateKey(sPubKey string) { func (e *ECDH) generateKey(sPubKey string) {
pub, err := hex.DecodeString(sPubKey) pub, _ := hex.DecodeString(sPubKey)
if err != nil {
panic(err)
}
p256 := elliptic.P256() p256 := elliptic.P256()
key, sx, sy, err := elliptic.GenerateKey(p256, rand.Reader) key, sx, sy, _ := elliptic.GenerateKey(p256, rand.Reader)
if err != nil {
panic(err)
}
tx, ty := elliptic.Unmarshal(p256, pub) tx, ty := elliptic.Unmarshal(p256, pub)
x, _ := p256.ScalarMult(tx, ty, key) x, _ := p256.ScalarMult(tx, ty, key)
hash := md5.Sum(x.Bytes()[:16]) hash := md5.Sum(x.Bytes()[:16])
e.InitialShareKey = hash[:] e.ShareKey = hash[:]
e.PublicKey = elliptic.Marshal(p256, sx, sy) 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 { type pubKeyResp struct {
Meta struct { Meta struct {
PubKeyVer uint16 `json:"KeyVer"` PubKeyVer uint16 `json:"KeyVer"`
@ -91,7 +49,7 @@ type pubKeyResp struct {
} }
// FetchPubKey 从服务器获取PubKey // 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)) resp, err := http.Get("https://keyrotate.qq.com/rotate_key?cipher_suite_ver=305&uin=" + strconv.FormatInt(uin, 10))
if err != nil { if err != nil {
return return
@ -102,6 +60,6 @@ func (e *EncryptECDH) FetchPubKey(uin int64) {
if err != nil { if err != nil {
return return
} }
e.PublicKeyVer = pubKey.Meta.PubKeyVer e.SvrPublicKeyVer = pubKey.Meta.PubKeyVer
e.generateKey(pubKey.Meta.PubKey) // todo check key sign e.generateKey(pubKey.Meta.PubKey) // todo check key sign
} }