diff --git a/client/builders.go b/client/builders.go index 4abe6b95..ea01fe00 100644 --- a/client/builders.go +++ b/client/builders.go @@ -8,7 +8,6 @@ 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/network" "github.com/Mrs4s/MiraiGo/client/internal/oicq" "github.com/Mrs4s/MiraiGo/client/pb" @@ -19,6 +18,7 @@ import ( "github.com/Mrs4s/MiraiGo/client/pb/structmsg" "github.com/Mrs4s/MiraiGo/internal/proto" "github.com/Mrs4s/MiraiGo/internal/tlv" + "github.com/Mrs4s/MiraiGo/wrapper" ) var ( @@ -102,6 +102,25 @@ func (c *QQClient) buildLoginPacket() (uint16, []byte) { tlv.T521(0), tlv.T525(tlv.T536([]byte{0x01, 0x00})), ) + if wrapper.DandelionEnergy != nil { + salt := binary.NewWriterF(func(w *binary.Writer) { + // util.int64_to_buf(bArr42, 0, (int) uin2); + // util.int16_to_buf(bArr42, 4, u.guid.length); // 故意的还是不小心的 + w.Write(binary.NewWriterF(func(w *binary.Writer) { w.WriteUInt64(uint64(c.Uin)) })[:4]) + w.WriteBytesShort(c.Device().Guid) + w.WriteBytesShort([]byte(c.version().SdkVersion)) + w.WriteUInt32(9) // sub command + w.WriteUInt32(0) // 被演了 + }) + if t544 := tlv.T544Custom(uint64(c.Uin), "810_9", salt, wrapper.DandelionEnergy); t544 != nil { + t.Append(t544) + } + } + if c.Device().QImei36 != "" { + t.Append(tlv.T545([]byte(c.Device().QImei16))) + } else { + t.Append(tlv.T545([]byte(c.Device().IMEI))) + } req := c.buildOicqRequestPacket(c.Uin, 0x0810, t) r := network.Request{ Type: network.RequestTypeLogin, @@ -137,17 +156,15 @@ func (c *QQClient) buildDeviceLockLoginPacket() (uint16, []byte) { } func (c *QQClient) buildQRCodeFetchRequestPacket(size, margin, ecLevel uint32) (uint16, []byte) { - old := c.version() - watch := auth.AndroidWatch.Version() - c.transport.Version = watch + // old := c.version() + // watch := auth.AndroidWatch.Version() + // c.transport.Version = watch seq := c.nextSeq() req := oicq.Message{ Command: 0x0812, EncryptionMethod: oicq.EM_ECDH, Body: binary.NewWriterF(func(w *binary.Writer) { - w.WriteHex(`0001110000001000000072000000`) // trans header - w.WriteUInt32(uint32(time.Now().Unix())) - w.Write(buildCode2DRequestPacket(0, 0, 0x31, func(w *binary.Writer) { + code2dPacket := buildCode2DRequestPacket(0, 0, 0x31, func(w *binary.Writer) { w.WriteUInt16(0) // const w.WriteUInt32(16) // app id w.WriteUInt64(0) // const @@ -155,13 +172,20 @@ func (c *QQClient) buildQRCodeFetchRequestPacket(size, margin, ecLevel uint32) ( w.WriteBytesShort(EmptyBytes) w.WriteUInt16(6) - w.Write(tlv.T16(watch.SSOVersion, 16, watch.AppId, c.Device().Guid, []byte(watch.ApkId), []byte(watch.SortVersionName), watch.ApkSign)) + w.Write(tlv.T16(c.transport.Version.SSOVersion, 16, c.transport.Version.AppId, c.Device().Guid, []byte(c.transport.Version.ApkId), []byte(c.transport.Version.SortVersionName), c.transport.Version.ApkSign)) w.Write(tlv.T1B(0, 0, size, margin, 72, ecLevel, 2)) - w.Write(tlv.T1D(watch.MiscBitmap)) + w.Write(tlv.T1D(c.transport.Version.MiscBitmap)) w.Write(tlv.T1F(false, c.Device().OSType, []byte("7.1.2"), []byte("China Mobile GSM"), c.Device().APN, 2)) w.Write(tlv.T33(c.Device().Guid)) w.Write(tlv.T35(8)) - })) + }) + w.WriteByte(0x0) + w.WriteUInt16(uint16(len(code2dPacket)) + 4) + w.WriteUInt32(16) + w.WriteUInt32(0x72) + w.WriteHex("000000") + w.WriteUInt32(uint32(time.Now().Unix())) + w.Write(code2dPacket) }), } r := network.Request{ @@ -173,21 +197,19 @@ func (c *QQClient) buildQRCodeFetchRequestPacket(size, margin, ecLevel uint32) ( Body: c.oicq.Marshal(&req), } payload := c.transport.PackPacket(&r) - c.transport.Version = old + // c.transport.Version = old return seq, payload } func (c *QQClient) buildQRCodeResultQueryRequestPacket(sig []byte) (uint16, []byte) { - old := c.version() - c.transport.Version = auth.AndroidWatch.Version() + // old := c.version() + // c.transport.Version = auth.AndroidWatch.Version() seq := c.nextSeq() req := oicq.Message{ Command: 0x0812, EncryptionMethod: oicq.EM_ECDH, Body: binary.NewWriterF(func(w *binary.Writer) { - w.WriteHex(`0000620000001000000072000000`) // trans header - w.WriteUInt32(uint32(time.Now().Unix())) - w.Write(buildCode2DRequestPacket(1, 0, 0x12, func(w *binary.Writer) { + code2dPacket := buildCode2DRequestPacket(1, 0, 0x12, func(w *binary.Writer) { w.WriteUInt16(5) // const w.WriteByte(1) // const w.WriteUInt32(8) // product type @@ -197,7 +219,14 @@ func (c *QQClient) buildQRCodeResultQueryRequestPacket(sig []byte) (uint16, []by w.WriteByte(8) // const w.WriteBytesShort(EmptyBytes) w.WriteUInt16(0) // const - })) + }) + w.WriteByte(0x0) + w.WriteUInt16(uint16(len(code2dPacket)) + 4) + w.WriteUInt32(16) + w.WriteUInt32(0x72) + w.WriteHex("000000") + w.WriteUInt32(uint32(time.Now().Unix())) + w.Write(code2dPacket) }), } r := network.Request{ @@ -209,7 +238,7 @@ func (c *QQClient) buildQRCodeResultQueryRequestPacket(sig []byte) (uint16, []by Body: c.oicq.Marshal(&req), } payload := c.transport.PackPacket(&r) - c.transport.Version = old + // c.transport.Version = old return seq, payload } @@ -326,7 +355,7 @@ func (c *QQClient) buildSMSRequestPacket() (uint16, []byte) { func (c *QQClient) buildSMSCodeSubmitPacket(code string) (uint16, []byte) { seq := c.nextSeq() - req := c.buildOicqRequestPacket(c.Uin, 0x0810, &oicq.TLV{ + t := &oicq.TLV{ Command: 7, List: [][]byte{ tlv.T8(2052), @@ -337,7 +366,13 @@ func (c *QQClient) buildSMSCodeSubmitPacket(code string) (uint16, []byte) { tlv.T401(c.sig.G), tlv.T198(), }, - }) + } + if wrapper.DandelionEnergy != nil { + if t544 := tlv.T544(uint64(c.Uin), "810_7", 7, c.version().SdkVersion, c.Device().Guid, wrapper.DandelionEnergy); t544 != nil { + t.Append(t544) + } + } + req := c.buildOicqRequestPacket(c.Uin, 0x0810, t) r := network.Request{ Type: network.RequestTypeLogin, EncryptType: network.EncryptTypeEmptyKey, @@ -363,6 +398,11 @@ func (c *QQClient) buildTicketSubmitPacket(ticket string) (uint16, []byte) { if c.sig.T547 != nil { t.Append(tlv.T(0x547, c.sig.T547)) } + if wrapper.DandelionEnergy != nil { + if t544 := tlv.T544(uint64(c.Uin), "810_2", 2, c.version().SdkVersion, c.Device().Guid, wrapper.DandelionEnergy); t544 != nil { + t.Append(t544) + } + } req := c.buildOicqRequestPacket(c.Uin, 0x0810, t) r := network.Request{ Type: network.RequestTypeLogin, @@ -421,16 +461,20 @@ func (c *QQClient) buildRequestTgtgtNopicsigPacket() (uint16, []byte) { tlv.T516(), tlv.T521(0), tlv.T525(tlv.T536([]byte{0x01, 0x00})), - tlv.T545([]byte(c.Device().IMEI)), }, } + if c.Device().QImei36 != "" { + t.Append(tlv.T545([]byte(c.Device().QImei16))) + } else { + t.Append(tlv.T545([]byte(c.Device().IMEI))) + } m := oicq.Message{ Uin: uint32(c.Uin), Command: 0x810, EncryptionMethod: oicq.EM_ST, Body: t.Marshal(), } - nreq := network.Request{ + req := network.Request{ Type: network.RequestTypeSimple, EncryptType: network.EncryptTypeEmptyKey, Uin: c.Uin, @@ -438,7 +482,7 @@ func (c *QQClient) buildRequestTgtgtNopicsigPacket() (uint16, []byte) { CommandName: "wtlogin.exchange_emp", Body: c.oicq.Marshal(&m), } - return seq, c.transport.PackPacket(&nreq) + return seq, c.transport.PackPacket(&req) } func (c *QQClient) buildRequestChangeSigPacket(changeD2 bool) (uint16, []byte) { diff --git a/client/client.go b/client/client.go index 69b8aa12..9f0d9579 100644 --- a/client/client.go +++ b/client/client.go @@ -106,6 +106,8 @@ type QQClient struct { GroupDigestEvent EventHandle[*GroupDigestEvent] OtherClientStatusChangedEvent EventHandle[*OtherClientStatusChangedEvent] OfflineFileEvent EventHandle[*OfflineFileEvent] + GroupDisbandEvent EventHandle[*GroupDisbandEvent] + DeleteFriendEvent EventHandle[*DeleteFriendEvent] // message state msgSvcCache *utils.Cache[unit] diff --git a/client/decoders.go b/client/decoders.go index 789b6cb1..c6593cda 100644 --- a/client/decoders.go +++ b/client/decoders.go @@ -51,6 +51,7 @@ func decodeLoginResponse(c *QQClient, pkt *network.Packet) (any, error) { if m.Exists(0x546) { c.sig.T547 = auth.CalcPow(m[0x546]) } + // c.logger.Info("login response %v", t) if t == 0 { // login success // if t150, ok := m[0x150]; ok { // c.t150 = t150 @@ -71,6 +72,7 @@ func decodeLoginResponse(c *QQClient, pkt *network.Packet) (any, error) { if m.Exists(0x192) { return LoginResponse{ Success: false, + Code: t, VerifyUrl: string(m[0x192]), Error: SliderNeededError, }, nil @@ -82,6 +84,7 @@ func decodeLoginResponse(c *QQClient, pkt *network.Packet) (any, error) { sign := imgData.ReadBytes(int(signLen)) return LoginResponse{ Success: false, + Code: t, Error: NeedCaptcha, CaptchaImage: imgData.ReadAvailable(), CaptchaSign: sign, @@ -89,6 +92,7 @@ func decodeLoginResponse(c *QQClient, pkt *network.Packet) (any, error) { } else { return LoginResponse{ Success: false, + Code: t, Error: UnknownLoginError, }, nil } @@ -97,6 +101,7 @@ func decodeLoginResponse(c *QQClient, pkt *network.Packet) (any, error) { if t == 40 { return LoginResponse{ Success: false, + Code: t, ErrorMessage: "账号被冻结", Error: UnknownLoginError, }, nil @@ -115,6 +120,7 @@ func decodeLoginResponse(c *QQClient, pkt *network.Packet) (any, error) { if t204, ok := m[0x204]; ok { // 同时支持扫码验证 ? return LoginResponse{ Success: false, + Code: t, Error: SMSOrVerifyNeededError, VerifyUrl: string(t204), SMSPhone: phone, @@ -123,6 +129,7 @@ func decodeLoginResponse(c *QQClient, pkt *network.Packet) (any, error) { } return LoginResponse{ Success: false, + Code: t, Error: SMSNeededError, SMSPhone: phone, ErrorMessage: string(m[0x17e]), @@ -133,6 +140,7 @@ func decodeLoginResponse(c *QQClient, pkt *network.Packet) (any, error) { c.sig.T104 = m[0x104] return LoginResponse{ Success: false, + Code: t, Error: SMSNeededError, }, nil } @@ -140,6 +148,7 @@ func decodeLoginResponse(c *QQClient, pkt *network.Packet) (any, error) { if t204, ok := m[0x204]; ok { // 扫码验证 return LoginResponse{ Success: false, + Code: t, Error: UnsafeDeviceError, VerifyUrl: string(t204), ErrorMessage: "", @@ -149,6 +158,7 @@ func decodeLoginResponse(c *QQClient, pkt *network.Packet) (any, error) { if t == 162 { return LoginResponse{ + Code: t, Error: TooManySMSRequestError, }, nil } @@ -165,6 +175,7 @@ func decodeLoginResponse(c *QQClient, pkt *network.Packet) (any, error) { t149r.ReadStringShort() // title return LoginResponse{ Success: false, + Code: t, Error: OtherLoginError, ErrorMessage: t149r.ReadStringShort(), }, nil @@ -176,6 +187,7 @@ func decodeLoginResponse(c *QQClient, pkt *network.Packet) (any, error) { t146r.ReadStringShort() // title return LoginResponse{ Success: false, + Code: t, Error: OtherLoginError, ErrorMessage: t146r.ReadStringShort(), }, nil @@ -732,6 +744,15 @@ func decodeOnlinePushTransPacket(c *QQClient, pkt *network.Packet) (any, error) Operator: g.FindMember(operator), }) } + case 0x01, 0x81: // kosbot add: 群解散. 暂时这样 See https://github.com/lz1998/ricq/blob/064ddddca19aa0410e2514852e3a151fd9913371/ricq-core/src/command/online_push/decoder.rs#L86 + c.GroupDisbandEvent.dispatch(c, &GroupDisbandEvent{ + Group: g, + Operator: g.FindMember(operator), + Time: int64(info.MsgTime.Unwrap()), + }) + if err = c.ReloadGroupList(); err != nil { + return nil, err + } } } } diff --git a/client/entities.go b/client/entities.go index b1e312ce..463d1cb9 100644 --- a/client/entities.go +++ b/client/entities.go @@ -23,10 +23,11 @@ type ( UserOnlineStatus int - ClientProtocol = auth.Protocol + ClientProtocol = auth.ProtocolType LoginResponse struct { Success bool + Code byte Error LoginError // Captcha info @@ -191,6 +192,17 @@ type ( DownloadUrl string } + GroupDisbandEvent struct { + Group *GroupInfo + Time int64 + Operator *GroupMemberInfo + } + + DeleteFriendEvent struct { + Uin int64 + Nickname string + } + // GroupDigest 群精华消息 GroupDigest struct { GroupCode int64 `json:"group_code,string"` diff --git a/client/global.go b/client/global.go index 5cb62e57..c13f4108 100644 --- a/client/global.go +++ b/client/global.go @@ -81,6 +81,7 @@ func GenRandomDevice() *DeviceInfo { hex.Encode(device.AndroidId, r) device.GenNewGuid() device.GenNewTgtgtKey() + device.RequestQImei() return device } @@ -105,6 +106,13 @@ func GenIMEI() string { return final.String() } +func UpdateAppVersion(protocolType auth.ProtocolType, data []byte) error { + if _, ok := auth.AppVersions[protocolType]; !ok { + return errors.New("unknown protocol type: " + strconv.Itoa(int(protocolType))) + } + return auth.AppVersions[protocolType].ReadJson(data) +} + func getSSOAddress(device *auth.Device) ([]netip.AddrPort, error) { protocol := device.Protocol.Version() key, _ := hex.DecodeString("F0441F5FF42DA58FDCF7949ABA62D411") diff --git a/client/internal/auth/auth.go b/client/internal/auth/auth.go index 5e61d35a..f2ca18b8 100644 --- a/client/internal/auth/auth.go +++ b/client/internal/auth/auth.go @@ -1,16 +1,119 @@ package auth -//go:generate stringer -type=Protocol -linecomment -type Protocol int +import ( + "encoding/hex" + "encoding/json" + "fmt" + + "github.com/pkg/errors" +) + +//go:generate stringer -type=ProtocolType -linecomment +type ProtocolType int const ( - Unset Protocol = iota - AndroidPhone // Android Phone - AndroidWatch // Android Watch - MacOS // MacOS - QiDian // 企点 - IPad // iPad - AndroidPad // Android Pad + Unset ProtocolType = iota + AndroidPhone // Android Phone + AndroidWatch // Android Watch + MacOS // MacOS + QiDian // 企点 + IPad // iPad + AndroidPad // Android Pad +) + +var ( + AppVersions = map[ProtocolType]*AppVersion{ + AndroidPhone: { + ApkId: "com.tencent.mobileqq", + AppId: 537151682, + SubAppId: 537151682, + AppKey: "0S200MNJT807V3GE", + SortVersionName: "8.9.33.10335", + BuildTime: 1673599898, + ApkSign: []byte{0xA6, 0xB7, 0x45, 0xBF, 0x24, 0xA2, 0xC2, 0x77, 0x52, 0x77, 0x16, 0xF6, 0xF3, 0x6E, 0xB6, 0x8D}, + SdkVersion: "6.0.0.2534", + SSOVersion: 19, + MiscBitmap: 150470524, + SubSigmap: 0x10400, + MainSigMap: WLOGIN_A5 | WLOGIN_RESERVED | WLOGIN_STWEB | WLOGIN_A2 | WLOGIN_ST | + WLOGIN_LSKEY | WLOGIN_SKEY | WLOGIN_SIG64 | 1<<16 | WLOGIN_VKEY | WLOGIN_D2 | + WLOGIN_SID | WLOGIN_PSKEY | WLOGIN_AQSIG | WLOGIN_LHSIG | WLOGIN_PAYTOKEN, // 16724722 + Protocol: AndroidPhone, + }, + AndroidPad: { + ApkId: "com.tencent.mobileqq", + AppId: 537151218, + SubAppId: 537151218, + AppKey: "0S200MNJT807V3GE", + SortVersionName: "8.9.33.10335", + BuildTime: 1673599898, + ApkSign: []byte{0xA6, 0xB7, 0x45, 0xBF, 0x24, 0xA2, 0xC2, 0x77, 0x52, 0x77, 0x16, 0xF6, 0xF3, 0x6E, 0xB6, 0x8D}, + SdkVersion: "6.0.0.2534", + SSOVersion: 19, + MiscBitmap: 150470524, + SubSigmap: 0x10400, + MainSigMap: WLOGIN_A5 | WLOGIN_RESERVED | WLOGIN_STWEB | WLOGIN_A2 | WLOGIN_ST | + WLOGIN_LSKEY | WLOGIN_SKEY | WLOGIN_SIG64 | 1<<16 | WLOGIN_VKEY | WLOGIN_D2 | + WLOGIN_SID | WLOGIN_PSKEY | WLOGIN_AQSIG | WLOGIN_LHSIG | WLOGIN_PAYTOKEN, // 16724722 + Protocol: AndroidPad, + }, + AndroidWatch: { + ApkId: "com.tencent.qqlite", + AppId: 537064446, + SubAppId: 537064446, + SortVersionName: "2.0.5", + BuildTime: 1559564731, + ApkSign: []byte{0xA6, 0xB7, 0x45, 0xBF, 0x24, 0xA2, 0xC2, 0x77, 0x52, 0x77, 0x16, 0xF6, 0xF3, 0x6E, 0xB6, 0x8D}, + SdkVersion: "6.0.0.236", + SSOVersion: 5, + MiscBitmap: 16252796, + SubSigmap: 0x10400, + MainSigMap: WLOGIN_STWEB | WLOGIN_A2 | WLOGIN_ST | WLOGIN_SKEY | WLOGIN_D2 | WLOGIN_PSKEY | WLOGIN_DA2, // 34869472 + Protocol: AndroidWatch, + }, + IPad: { + ApkId: "com.tencent.minihd.qq", + AppId: 537118796, + SubAppId: 537118796, + SortVersionName: "5.9.3", + BuildTime: 1595836208, + ApkSign: []byte{170, 57, 120, 244, 31, 217, 111, 249, 145, 74, 102, 158, 24, 100, 116, 199}, + SdkVersion: "6.0.0.2433", + SSOVersion: 12, + MiscBitmap: 150470524, + SubSigmap: 66560, + MainSigMap: WLOGIN_STWEB | WLOGIN_A2 | WLOGIN_ST | WLOGIN_SKEY | WLOGIN_VKEY | WLOGIN_D2 | WLOGIN_SID | WLOGIN_PSKEY, // 1970400 + Protocol: IPad, + }, + MacOS: { + ApkId: "com.tencent.minihd.qq", + AppId: 537128930, + SubAppId: 537128930, + SortVersionName: "5.8.9", + BuildTime: 1595836208, + ApkSign: []byte{170, 57, 120, 244, 31, 217, 111, 249, 145, 74, 102, 158, 24, 100, 116, 199}, + SdkVersion: "6.0.0.2433", + SSOVersion: 12, + MiscBitmap: 150470524, + SubSigmap: 66560, + MainSigMap: WLOGIN_STWEB | WLOGIN_A2 | WLOGIN_ST | WLOGIN_SKEY | WLOGIN_VKEY | WLOGIN_D2 | WLOGIN_SID | WLOGIN_PSKEY, // 1970400 + Protocol: MacOS, + }, + QiDian: { + ApkId: "com.tencent.qidian", + AppId: 537096038, + SubAppId: 537036590, + SortVersionName: "5.0.0", + BuildTime: 1630062176, + ApkSign: []byte{160, 30, 236, 171, 133, 233, 227, 186, 43, 15, 106, 21, 140, 133, 92, 41}, + SdkVersion: "6.0.0.2484", + SSOVersion: 18, + MiscBitmap: 184024956, + SubSigmap: 66560, + MainSigMap: WLOGIN_STWEB | WLOGIN_A2 | WLOGIN_ST | WLOGIN_SKEY | WLOGIN_D2 | WLOGIN_PSKEY | WLOGIN_DA2, // 34869472 + Protocol: QiDian, + }, + } ) // see oicq/wlogin_sdk/request/WtloginHelper.java class SigType @@ -52,128 +155,44 @@ type AppVersion struct { SdkVersion string AppId uint32 SubAppId uint32 + AppKey string BuildTime uint32 SSOVersion uint32 MiscBitmap uint32 SubSigmap uint32 MainSigMap uint32 - Protocol Protocol + Protocol ProtocolType } -var ( - aPhone = &AppVersion{ - ApkId: "com.tencent.mobileqq", - AppId: 537143097, - SubAppId: 537143097, - SortVersionName: "8.9.23.9425", - BuildTime: 1640921786, - ApkSign: []byte{0xA6, 0xB7, 0x45, 0xBF, 0x24, 0xA2, 0xC2, 0x77, 0x52, 0x77, 0x16, 0xF6, 0xF3, 0x6E, 0xB6, 0x8D}, - SdkVersion: "6.0.0.2530", - SSOVersion: 19, - MiscBitmap: 150470524, - SubSigmap: 0x10400, - MainSigMap: WLOGIN_A5 | WLOGIN_RESERVED | WLOGIN_STWEB | WLOGIN_A2 | WLOGIN_ST | - WLOGIN_LSKEY | WLOGIN_SKEY | WLOGIN_SIG64 | 1<<16 | WLOGIN_VKEY | WLOGIN_D2 | - WLOGIN_SID | WLOGIN_PSKEY | WLOGIN_AQSIG | WLOGIN_LHSIG | WLOGIN_PAYTOKEN, // 16724722 - Protocol: AndroidPhone, - } +func (v *AppVersion) String() string { + return fmt.Sprintf("%s %s - %v", v.Protocol.String(), v.SortVersionName, v.BuildTime) +} - aPad = &AppVersion{ - ApkId: "com.tencent.mobileqq", - AppId: 537142586, - SubAppId: 537142586, - SortVersionName: "8.9.23.9425", - BuildTime: 1640921786, - ApkSign: []byte{0xA6, 0xB7, 0x45, 0xBF, 0x24, 0xA2, 0xC2, 0x77, 0x52, 0x77, 0x16, 0xF6, 0xF3, 0x6E, 0xB6, 0x8D}, - SdkVersion: "6.0.0.2530", - SSOVersion: 19, - MiscBitmap: 150470524, - SubSigmap: 0x10400, - MainSigMap: WLOGIN_A5 | WLOGIN_RESERVED | WLOGIN_STWEB | WLOGIN_A2 | WLOGIN_ST | - WLOGIN_LSKEY | WLOGIN_SKEY | WLOGIN_SIG64 | 1<<16 | WLOGIN_VKEY | WLOGIN_D2 | - WLOGIN_SID | WLOGIN_PSKEY | WLOGIN_AQSIG | WLOGIN_LHSIG | WLOGIN_PAYTOKEN, // 16724722 - Protocol: AndroidPad, - } - - aWatch = &AppVersion{ - ApkId: "com.tencent.qqlite", - AppId: 537064446, - SubAppId: 537064446, - SortVersionName: "2.0.5", - BuildTime: 1559564731, - ApkSign: []byte{0xA6, 0xB7, 0x45, 0xBF, 0x24, 0xA2, 0xC2, 0x77, 0x52, 0x77, 0x16, 0xF6, 0xF3, 0x6E, 0xB6, 0x8D}, - SdkVersion: "6.0.0.236", - SSOVersion: 5, - MiscBitmap: 16252796, - SubSigmap: 0x10400, - MainSigMap: WLOGIN_STWEB | WLOGIN_A2 | WLOGIN_ST | WLOGIN_SKEY | WLOGIN_D2 | WLOGIN_PSKEY | WLOGIN_DA2, // 34869472 - Protocol: AndroidWatch, - } - - ipad = &AppVersion{ - ApkId: "com.tencent.minihd.qq", - AppId: 537118796, - SubAppId: 537118796, - SortVersionName: "5.9.3", - BuildTime: 1595836208, - ApkSign: []byte{170, 57, 120, 244, 31, 217, 111, 249, 145, 74, 102, 158, 24, 100, 116, 199}, - SdkVersion: "6.0.0.2433", - SSOVersion: 12, - MiscBitmap: 150470524, - SubSigmap: 66560, - MainSigMap: WLOGIN_STWEB | WLOGIN_A2 | WLOGIN_ST | WLOGIN_SKEY | WLOGIN_VKEY | WLOGIN_D2 | WLOGIN_SID | WLOGIN_PSKEY, // 1970400 - Protocol: IPad, - } - - macOS = &AppVersion{ - ApkId: "com.tencent.minihd.qq", - AppId: 537128930, - SubAppId: 537128930, - SortVersionName: "5.8.9", - BuildTime: 1595836208, - ApkSign: []byte{170, 57, 120, 244, 31, 217, 111, 249, 145, 74, 102, 158, 24, 100, 116, 199}, - SdkVersion: "6.0.0.2433", - SSOVersion: 12, - MiscBitmap: 150470524, - SubSigmap: 66560, - MainSigMap: WLOGIN_STWEB | WLOGIN_A2 | WLOGIN_ST | WLOGIN_SKEY | WLOGIN_VKEY | WLOGIN_D2 | WLOGIN_SID | WLOGIN_PSKEY, // 1970400 - Protocol: MacOS, - } - - qidian = &AppVersion{ - ApkId: "com.tencent.qidian", - AppId: 537096038, - SubAppId: 537036590, - SortVersionName: "5.0.0", - BuildTime: 1630062176, - ApkSign: []byte{160, 30, 236, 171, 133, 233, 227, 186, 43, 15, 106, 21, 140, 133, 92, 41}, - SdkVersion: "6.0.0.2484", - SSOVersion: 18, - MiscBitmap: 184024956, - SubSigmap: 66560, - MainSigMap: WLOGIN_STWEB | WLOGIN_A2 | WLOGIN_ST | WLOGIN_SKEY | WLOGIN_D2 | WLOGIN_PSKEY | WLOGIN_DA2, // 34869472 - Protocol: QiDian, - } -) - -func (i Protocol) Version() *AppVersion { - switch i { - case AndroidPhone: - return aPhone - case AndroidPad: - return aPad - case AndroidWatch: - return aWatch - case IPad: - return ipad - case MacOS: - return macOS - case QiDian: - return qidian +func (v *AppVersion) ReadJson(d []byte) error { + var f appVersionFile + if err := json.Unmarshal(d, &f); err != nil { + return errors.Wrap(err, "failed to unmarshal json message") } + v.ApkId = f.ApkId + v.AppId = f.AppId + v.SubAppId = f.SubAppId + v.AppKey = f.AppKey + v.SortVersionName = f.SortVersionName + v.BuildTime = f.BuildTime + v.ApkSign, _ = hex.DecodeString(f.ApkSign) + v.SdkVersion = f.SdkVersion + v.SSOVersion = f.SSOVersion + v.MiscBitmap = f.MiscBitmap + v.SubSigmap = f.SubSigmap + v.MainSigMap = f.MainSigMap + v.Protocol = f.ProtocolType return nil } +func (i ProtocolType) Version() *AppVersion { + return AppVersions[i] +} + type SigInfo struct { LoginBitmap uint64 TGT []byte @@ -216,3 +235,19 @@ type SigInfo struct { Ksid []byte // msgCtrlBuf []byte } + +type appVersionFile struct { + ApkId string `json:"apk_id"` + AppId uint32 `json:"app_id"` + SubAppId uint32 `json:"sub_app_id"` + AppKey string `json:"app_key"` + SortVersionName string `json:"sort_version_name"` + BuildTime uint32 `json:"build_time"` + ApkSign string `json:"apk_sign"` // hex encoded + SdkVersion string `json:"sdk_version"` + SSOVersion uint32 `json:"sso_version"` + MiscBitmap uint32 `json:"misc_bitmap"` + MainSigMap uint32 `json:"main_sig_map"` + SubSigmap uint32 `json:"sub_sig_map"` + ProtocolType ProtocolType `json:"protocol_type"` +} diff --git a/client/internal/auth/device.go b/client/internal/auth/device.go index 8bd06ae8..35acc2ef 100644 --- a/client/internal/auth/device.go +++ b/client/internal/auth/device.go @@ -45,7 +45,9 @@ type Device struct { VendorOSName []byte Guid []byte TgtgtKey []byte - Protocol Protocol + QImei16 string + QImei36 string + Protocol ProtocolType Version *OSVersion } @@ -135,7 +137,7 @@ func (info *Device) ReadJson(d []byte) error { switch f.Protocol { case 1, 2, 3, 4, 5, 6: - info.Protocol = Protocol(f.Protocol) + info.Protocol = ProtocolType(f.Protocol) default: info.Protocol = AndroidPad } @@ -149,6 +151,7 @@ func (info *Device) ReadJson(d []byte) error { info.GenNewGuid() info.GenNewTgtgtKey() + info.RequestQImei() // 应该可以缓存, 理论上同一设备每次请求都是一样的 return nil } diff --git a/client/internal/auth/protocol_string.go b/client/internal/auth/protocol_string.go deleted file mode 100644 index 2f8deec6..00000000 --- a/client/internal/auth/protocol_string.go +++ /dev/null @@ -1,29 +0,0 @@ -// Code generated by "stringer -type=Protocol -linecomment"; DO NOT EDIT. - -package auth - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[Unset-0] - _ = x[AndroidPhone-1] - _ = x[AndroidWatch-2] - _ = x[MacOS-3] - _ = x[QiDian-4] - _ = x[IPad-5] - _ = x[AndroidPad-6] -} - -const _Protocol_name = "UnsetAndroid PhoneAndroid WatchMacOS企点iPadAndroid Pad" - -var _Protocol_index = [...]uint8{0, 5, 18, 31, 36, 42, 46, 57} - -func (i Protocol) String() string { - if i < 0 || i >= Protocol(len(_Protocol_index)-1) { - return "Protocol(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _Protocol_name[_Protocol_index[i]:_Protocol_index[i+1]] -} diff --git a/client/internal/auth/protocoltype_string.go b/client/internal/auth/protocoltype_string.go new file mode 100644 index 00000000..538680f0 --- /dev/null +++ b/client/internal/auth/protocoltype_string.go @@ -0,0 +1,29 @@ +// Code generated by "stringer -type=ProtocolType -linecomment"; DO NOT EDIT. + +package auth + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[Unset-0] + _ = x[AndroidPhone-1] + _ = x[AndroidWatch-2] + _ = x[MacOS-3] + _ = x[QiDian-4] + _ = x[IPad-5] + _ = x[AndroidPad-6] +} + +const _ProtocolType_name = "UnsetAndroid PhoneAndroid WatchMacOS企点iPadAndroid Pad" + +var _ProtocolType_index = [...]uint8{0, 5, 18, 31, 36, 42, 46, 57} + +func (i ProtocolType) String() string { + if i < 0 || i >= ProtocolType(len(_ProtocolType_index)-1) { + return "ProtocolType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _ProtocolType_name[_ProtocolType_index[i]:_ProtocolType_index[i+1]] +} diff --git a/client/internal/auth/qimei.go b/client/internal/auth/qimei.go new file mode 100644 index 00000000..7685cb17 --- /dev/null +++ b/client/internal/auth/qimei.go @@ -0,0 +1,192 @@ +package auth + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/md5" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/hex" + "encoding/json" + "encoding/pem" + "fmt" + "math/rand" + "time" + + "github.com/Mrs4s/MiraiGo/utils" + "github.com/tidwall/gjson" +) + +const ( + secret = "ZdJqM15EeO2zWc08" + rsaKey = `-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEIxgwoutfwoJxcGQeedgP7FG9 +qaIuS0qzfR8gWkrkTZKM2iWHn2ajQpBRZjMSoSf6+KJGvar2ORhBfpDXyVtZCKpq +LQ+FLkpncClKVIrBwv6PHyUvuCb0rIarmgDnzkfQAqVufEtR64iazGDKatvJ9y6B +9NMbHddGSAUmRTCrHQIDAQAB +-----END PUBLIC KEY-----` +) + +func (info *Device) RequestQImei() { + if info.Protocol.Version().AppKey == "" { + return + } + + // init params + payload, _ := json.Marshal(genRandomPayloadByDevice(info)) + cryptKey := utils.RandomStringRange(16, "abcdef1234567890") + ts := time.Now().Unix() * 1000 + nonce := utils.RandomStringRange(16, "abcdef1234567890") + + // init rsa key and aes key + publicKey := initPublicKey() + encryptedAesKey, _ := rsa.EncryptPKCS1v15(rand.New(rand.NewSource(time.Now().UnixNano())), publicKey, []byte(cryptKey)) + + encryptedPayload := aesEncrypt(payload, []byte(cryptKey)) + + key := base64.StdEncoding.EncodeToString(encryptedAesKey) + params := base64.StdEncoding.EncodeToString(encryptedPayload) + + postData, _ := json.Marshal(map[string]any{ + "key": key, + "params": params, + "time": ts, + "nonce": nonce, + "sign": sign(key, params, fmt.Sprint(ts), nonce), + "extra": "", + }) + + resp, _ := utils.HttpPostBytesWithCookie("https://snowflake.qq.com/ola/android", postData, "", "application/json") + if gjson.GetBytes(resp, "code").Int() != 0 { + return + } + encryptedResponse, _ := base64.StdEncoding.DecodeString(gjson.GetBytes(resp, "data").String()) + decryptedResponse := aesDecrypt(encryptedResponse, []byte(cryptKey)) + info.QImei16 = gjson.GetBytes(decryptedResponse, "q16").String() + info.QImei36 = gjson.GetBytes(decryptedResponse, "q36").String() +} + +func initPublicKey() *rsa.PublicKey { + blockPub, _ := pem.Decode([]byte(rsaKey)) + pub, _ := x509.ParsePKIXPublicKey(blockPub.Bytes) + return pub.(*rsa.PublicKey) +} + +func sign(key, params, ts, nonce string) string { + h := md5.Sum([]byte(key + params + ts + nonce + secret)) + return hex.EncodeToString(h[:]) +} + +func aesEncrypt(src []byte, key []byte) []byte { + block, _ := aes.NewCipher(key) + ecb := cipher.NewCBCEncrypter(block, key) + content := src + content = pkcs5Padding(content, block.BlockSize()) + crypted := make([]byte, len(content)) + ecb.CryptBlocks(crypted, content) + return crypted +} + +func aesDecrypt(crypt []byte, key []byte) []byte { + block, _ := aes.NewCipher(key) + ecb := cipher.NewCBCDecrypter(block, key) + decrypted := make([]byte, len(crypt)) + ecb.CryptBlocks(decrypted, crypt) + return pkcs5Trimming(decrypted) +} + +func pkcs5Padding(ciphertext []byte, blockSize int) []byte { + padding := blockSize - len(ciphertext)%blockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(ciphertext, padtext...) +} + +func pkcs5Trimming(encrypt []byte) []byte { + padding := encrypt[len(encrypt)-1] + return encrypt[:len(encrypt)-int(padding)] +} + +func genRandomPayloadByDevice(info *Device) map[string]any { + now := time.Now() + seed := int64(0x6F4) + for _, b := range info.Guid { + seed += int64(b) + } + fixedRand := rand.New(rand.NewSource(seed)) + years := now.Year() + month := now.Month() + if month == 1 { + years-- + month = 12 + } else { + month-- + } + reserved := map[string]string{ + "harmony": "0", + "clone": "0", + "containe": "", + "oz": "UhYmelwouA+V2nPWbOvLTgN2/m8jwGB+yUB5v9tysQg=", + "oo": "Xecjt+9S1+f8Pz2VLSxgpw==", + "kelong": "0", + "uptimes": time.Date(years, month, fixedRand.Intn(27)+1, fixedRand.Intn(60), fixedRand.Intn(60), fixedRand.Intn(60), fixedRand.Intn(1e9), now.Location()).Format("2006-01-02 15:04:05"), + "multiUser": "0", + "bod": string(info.Board), + "brd": string(info.Brand), + "dv": string(info.Device), + "firstLevel": "", + "manufact": string(info.Brand), + "name": string(info.Model), + "host": "se.infra", + "kernel": string(info.ProcVersion), + } + reservedBytes, _ := json.Marshal(reserved) + deviceType := "Phone" + if info.Protocol == AndroidPad { + deviceType = "Pad" + } + beaconId := "" + timeMonth := time.Now().Format("2006-01-") + "01" + rand1 := fixedRand.Intn(899999) + 100000 + rand2 := fixedRand.Intn(899999999) + 100000000 + for i := 1; i <= 40; i++ { + switch i { + case 1, 2, 13, 14, 17, 18, 21, 22, 25, 26, 29, 30, 33, 34, 37, 38: + beaconId += fmt.Sprintf("k%v:%v%v.%v", i, timeMonth, rand1, rand2) + case 3: + beaconId += "k3:0000000000000000" + case 4: + beaconId += "k4:" + utils.RandomStringRange(16, "123456789abcdef") + default: + beaconId += fmt.Sprintf("k%v:%v", i, fixedRand.Intn(10000)) + } + beaconId += ";" + } + return map[string]any{ + "androidId": string(info.AndroidId), + "platformId": 1, + "appKey": info.Protocol.Version().AppKey, + "appVersion": info.Protocol.Version().SortVersionName, + "beaconIdSrc": beaconId, + "brand": string(info.Brand), + "channelId": "2017", + "cid": "", + "imei": info.IMEI, + "imsi": "", + "mac": "", + "model": string(info.Model), + "networkType": "unknown", + "oaid": "", + "osVersion": "Android " + string(info.Version.Release) + ",level " + fmt.Sprint(info.Version.SDK), + "qimei": "", + "qimei36": "", + "sdkVersion": "1.2.13.6", + "audit": "", + "userId": "{}", + "packageId": info.Protocol.Version().ApkId, + "deviceType": deviceType, + "sdkName": "", + "reserved": string(reservedBytes), + } +} diff --git a/client/online_push.go b/client/online_push.go index 3cc5560c..e87bc79a 100644 --- a/client/online_push.go +++ b/client/online_push.go @@ -208,6 +208,10 @@ func msgType0x210Sub27Decoder(c *QQClient, protobuf []byte) error { if m.DelFriend != nil { frdUin := m.DelFriend.Uins[0] if frd := c.FindFriend(int64(frdUin)); frd != nil { + c.DeleteFriendEvent.dispatch(c, &DeleteFriendEvent{ + Uin: frd.Uin, + Nickname: frd.Nickname, + }) if err := c.ReloadFriendList(); err != nil { return errors.Wrap(err, "failed to reload friend list") } diff --git a/internal/tlv/t544.go b/internal/tlv/t544.go new file mode 100644 index 00000000..fd71f6f8 --- /dev/null +++ b/internal/tlv/t544.go @@ -0,0 +1,33 @@ +package tlv + +import "github.com/Mrs4s/MiraiGo/binary" + +// temporary solution + +func T544(userId uint64, moduleId string, subCmd uint32, sdkVersion string, guid []byte, signer func(uint64, string, []byte) ([]byte, error)) []byte { + salt := binary.NewWriterF(func(w *binary.Writer) { + w.WriteUInt64(userId) + w.WriteBytesShort(guid) + w.WriteBytesShort([]byte(sdkVersion)) + w.WriteUInt32(subCmd) + }) + sign, err := signer(userId, moduleId, salt) + if err != nil { + return nil + } + return binary.NewWriterF(func(w *binary.Writer) { + w.WriteUInt16(0x544) + w.WriteBytesShort(sign) + }) +} + +func T544Custom(userId uint64, moduleId string, salt []byte, signer func(uint64, string, []byte) ([]byte, error)) []byte { + sign, err := signer(userId, moduleId, salt) + if err != nil { + return nil + } + return binary.NewWriterF(func(w *binary.Writer) { + w.WriteUInt16(0x544) + w.WriteBytesShort(sign) + }) +} diff --git a/internal/tlv/t545.go b/internal/tlv/t545.go index a916a047..3c155ec2 100644 --- a/internal/tlv/t545.go +++ b/internal/tlv/t545.go @@ -2,9 +2,9 @@ package tlv import "github.com/Mrs4s/MiraiGo/binary" -func T545(imei []byte) []byte { +func T545(qimei []byte) []byte { return binary.NewWriterF(func(w *binary.Writer) { w.WriteUInt16(0x545) - w.WriteBytesShort(imei) + w.WriteBytesShort(qimei) }) } diff --git a/wrapper/codec.go b/wrapper/codec.go new file mode 100644 index 00000000..b0b9055f --- /dev/null +++ b/wrapper/codec.go @@ -0,0 +1,3 @@ +package wrapper + +var DandelionEnergy func(uint64, string, []byte) ([]byte, error)