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/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)
}

View File

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

View File

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

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

View File

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

View File

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

View File

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