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:
parent
5dfa4528dd
commit
24b75e45c7
@ -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,11 +425,10 @@ func (c *QQClient) buildRequestTgtgtNopicsigPacket() (uint16, []byte) {
|
||||
w.Write(tlv.T545([]byte(c.deviceInfo.IMEI)))
|
||||
})
|
||||
|
||||
oicq := codec.OICQ{
|
||||
m := oicq.Message{
|
||||
Uin: uint32(c.Uin),
|
||||
Command: 0x810,
|
||||
EncryptMethod: crypto.NewEncryptSession(c.sig.T133),
|
||||
Key: c.sig.WtSessionTicketKey,
|
||||
EncryptionMethod: oicq.EM_ST,
|
||||
Body: req,
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
|
@ -122,7 +122,6 @@ type SigInfo struct {
|
||||
SKeyExpiredTime int64
|
||||
D2 []byte
|
||||
D2Key []byte
|
||||
WtSessionTicketKey []byte
|
||||
DeviceToken []byte
|
||||
|
||||
PsKeyMap map[string][]byte
|
||||
|
@ -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()...)
|
||||
}
|
130
client/internal/oicq/oicq.go
Normal file
130
client/internal/oicq/oicq.go
Normal 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
|
||||
}
|
@ -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)
|
||||
|
@ -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{
|
||||
req := oicq.Message{
|
||||
Uin: uint32(uin),
|
||||
Command: command,
|
||||
EncryptMethod: c.ecdh,
|
||||
Key: c.RandomKey,
|
||||
EncryptionMethod: oicq.EM_ECDH,
|
||||
Body: body,
|
||||
}
|
||||
return req.Encode()
|
||||
return c.oicq.Marshal(&req)
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user