C#WinForm实现雷速网站比赛MQTT逆向采集

1 篇文章 0 订阅
1 篇文章 0 订阅

目录

前言

一、寻找数据

二、C#WinForm程序编写MQTT客户端

总结



前言

提示: 本文仅用于学习分享,禁止用于任何违法行为,如有侵权,请联系删除。 

目标网址:足球比分_足球即时比分_足球比分直播-雷速体育

 列表中某一场进行中的比赛

 我们点开某一场,直接F12打开开发者面板

 刷新页面选中ws消息,链接上附带的是这场比赛的ID,可以看到网站的即时数据通过 MQTT 进行传输的,我们点开某一条消息,发现消息只是部分关键数据进行了加密,如下:

本篇文章主要内容就是解密加密的数据,使用C#WinForm程序进行获取数据 !


一、寻找数据

我们打开调用mqtt调用的代码进行观察:

 从上图中,观察到一个类似initConnect的方法,从意思上分析认为是初始化连接,当然不知道看的同学可以通过分析链接地址一步一步去找,最终都能找到这个方法!

我们直接单击进入方法中,下断点,再次刷新页面:

 我们可以看到,这个实际上做了一个 MQTT 连接的操作,至此我们拿到了MQTT地址以及需要的参数,参数一共有3个,clientId,username,password

关于clientid,我们下入断点,控制台输出相关数据:

 可以清楚看到这个clientid生成过程就是固定字符串(Leisu_P_0_)+24位随机数,经过后期软件的测试,这个值可以不填!

关于usernamepassword,我们在这这里下入断点,username单步调试进入,password控制台输出:

我们可以看到 ,这里密码是空字符串,用户名是通过执行了一个方法返回的,我们单步一部一部跟进:

跟到最后你会发现,这里的username就是一个固定字符串,由返回的js产生,经过后期软件的测试,这个值我们可以填这里的固定值!

了解MQTT的同学会知道,MQTT是基于发布订阅这样的一个模式,不知道的去百度了解一下,我们继续往下调试:

 上图中,我们可以看到在连接发生后,主动去订阅了四个主题:

live/football/detail/3657453/top_v2、live/football/detail/3657453/data_v2、live/football/detail/3657453/bet365、chatroom_v2/R1_3657453

那么订阅了就要有接收处理,这个初始化的方法还没有结束,下面代码中有json序列化的,猜测是接受处理数据的方法,控制台打印输出:

 果不其然,这里就是数据接受处理的地方,我们在下面这个方法中下入断点:

 最后我们得看到了输出的数据,这里把字节转换成了字符串,但是我们看到在输出的数据中,有一部分数据不是明文的,我们继续往下调试,找到解密的调用点:

在我们调试到这个地方的时候,可以看到明文数据已经解析出来了:

这里调用了一个方法 $.rot ,而方法需要两个参数,第一个是返回的加密的base64数据,第二个是一个整数,这个整数为接受到数据的数组第二项,即 i[2] ,看过我上一篇文章【C#实现雷速网站逆向】非常熟悉,这里调用的方法跟之前分析出来的解密方法一致,这篇文章就不多做阐述。那现在我们就拿到了链接,参数,数据解密方法,我们直接用C#WinForm程序进行编写。


二、C#WinForm程序编写MQTT客户端

我们先看最后的软件效果:

首先新建一个WinForm程序,nuget安装CefSharp(用于执行js加密),MQTTnet(用于mqtt客户端):

1.初始化CefSharp

     var setting = new CefSettings();

            // 设置语言
            setting.Locale = "zh-CN";
            //初始化配置
            CefSharp.Cef.Initialize(setting, true);

            //实例化浏览器对象
            chromeBrowser = new ChromiumWebBrowser();
            //浏览器添加到页面上
            this.panel1.Controls.Add(chromeBrowser);
            //平铺
            chromeBrowser.Dock = DockStyle.Fill;
            //打开空白页
            chromeBrowser.LoadUrl("about:blank");

2.初始化MQTT

  //实例化mqtt工厂
                    var factory = new MqttFactory();
                    //创建mqtt实例
                    var mqttClient = factory.CreateMqttClient();

                    //初始化mqtt客户端配置
                    var options = new MqttClientOptionsBuilder()
                            .WithWebSocketServer("wss://ws-gateway.leisu.com:443/mqtt")
                            .WithCredentials("leisu_user_rJD5hF8jJ3vW", "")
                            .Build();

                    //绑定连接成功委托方法
                    mqttClient.ConnectedHandler = new MqttClientConnectedHandlerDelegate(OnSubscriberConnected);
                    //绑定断开连接委托方法
                    mqttClient.DisconnectedHandler = new MqttClientDisconnectedHandlerDelegate(OnSubscriberDisconnected);
                    //绑定接受消息委托方法
                    mqttClient.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(OnSubscriberMessageReceived);

                    //连接
                    await mqttClient.ConnectAsync(options, CancellationToken.None); // Since 3.0.5 with 

                    //订阅-聊天
                    await mqttClient.SubscribeAsync($"chatroom_v2/R1_{matchId}");
                    //订阅-比赛趋势
                    await mqttClient.SubscribeAsync($"live/football/detail/{matchId}/data_v2");
                    订阅-bet365指数
                    await mqttClient.SubscribeAsync($"live/football/detail/{matchId}/bet365");
                    订阅-比赛数据
                    await mqttClient.SubscribeAsync($"live/football/detail/{matchId}/top_v2");



3.处理订阅委托方法

  private async Task OnSubscriberMessageReceived(MqttApplicationMessageReceivedEventArgs obj)
        {
            try
            {
                //接收到的Topic的消息
                var topic = obj.ApplicationMessage.Topic;

                //二进制转字符串
                var str = Encoding.UTF8.GetString(obj.ApplicationMessage.Payload);

                var json = Newtonsoft.Json.JsonConvert.DeserializeObject<JArray>(str);

                var num = json[2].ToString();

                //拼接调用函数
                var main = $"main('{json[1].ToString()}',{num})";


                //调用js拼接
                var js = jsStr.Replace("#Code#", "return " + main);

                //执行js返回结果
                var rel = await chromeBrowser.GetBrowser().MainFrame.EvaluateScriptAsync(js, null);

                var msg = "";

                #region 处理结果
                if (rel.Success)
                {
                    msg = Newtonsoft.Json.JsonConvert.SerializeObject(rel.Result);
                }
                else
                {
                    msg = rel.Message;

                }
                #endregion
                AppendText(topic + "【" + msg + "】");
            }
            catch (Exception ex)
            {
                AppendText(ex.ToString());
                Console.WriteLine(ex.ToString());
            }
        }

        private Task OnSubscriberDisconnected(MqttClientDisconnectedEventArgs obj)
        {
            AppendText("连接断开");
            return Task.FromResult(true);
        }

        private Task OnSubscriberConnected(MqttClientConnectedEventArgs obj)
        {
            AppendText("连接成功");
            return Task.FromResult(true);
        }

 4.最后附上调用js代码


(function () {

    /*! pako 2.0.4 https://github.com/nodeca/pako @license (MIT AND Zlib) */
    !function (e, t) { "object" == typeof exports && "undefined" != typeof module ? t(exports) : "function" == typeof define && define.amd ? define(["exports"], t) : t((e = "undefined" != typeof globalThis ? globalThis : e || self).pako = {}) }(this, (function (e) { "use strict"; var t = (e, t, i, n) => { let a = 65535 & e | 0, r = e >>> 16 & 65535 | 0, o = 0; for (; 0 !== i;) { o = i > 2e3 ? 2e3 : i, i -= o; do { a = a + t[n++] | 0, r = r + a | 0 } while (--o); a %= 65521, r %= 65521 } return a | r << 16 | 0 }; const i = new Uint32Array((() => { let e, t = []; for (var i = 0; i < 256; i++) { e = i; for (var n = 0; n < 8; n++)e = 1 & e ? 3988292384 ^ e >>> 1 : e >>> 1; t[i] = e } return t })()); var n = (e, t, n, a) => { const r = i, o = a + n; e ^= -1; for (let i = a; i < o; i++)e = e >>> 8 ^ r[255 & (e ^ t[i])]; return -1 ^ e }; var a = function (e, t) { let i, n, a, r, o, s, l, d, f, c, h, u, w, b, k, m, _, g, p, v, x, y, E, R; const A = e.state; i = e.next_in, E = e.input, n = i + (e.avail_in - 5), a = e.next_out, R = e.output, r = a - (t - e.avail_out), o = a + (e.avail_out - 257), s = A.dmax, l = A.wsize, d = A.whave, f = A.wnext, c = A.window, h = A.hold, u = A.bits, w = A.lencode, b = A.distcode, k = (1 << A.lenbits) - 1, m = (1 << A.distbits) - 1; e: do { u < 15 && (h += E[i++] << u, u += 8, h += E[i++] << u, u += 8), _ = w[h & k]; t: for (; ;) { if (g = _ >>> 24, h >>>= g, u -= g, g = _ >>> 16 & 255, 0 === g) R[a++] = 65535 & _; else { if (!(16 & g)) { if (0 == (64 & g)) { _ = w[(65535 & _) + (h & (1 << g) - 1)]; continue t } if (32 & g) { A.mode = 12; break e } e.msg = "invalid literal/length code", A.mode = 30; break e } p = 65535 & _, g &= 15, g && (u < g && (h += E[i++] << u, u += 8), p += h & (1 << g) - 1, h >>>= g, u -= g), u < 15 && (h += E[i++] << u, u += 8, h += E[i++] << u, u += 8), _ = b[h & m]; i: for (; ;) { if (g = _ >>> 24, h >>>= g, u -= g, g = _ >>> 16 & 255, !(16 & g)) { if (0 == (64 & g)) { _ = b[(65535 & _) + (h & (1 << g) - 1)]; continue i } e.msg = "invalid distance code", A.mode = 30; break e } if (v = 65535 & _, g &= 15, u < g && (h += E[i++] << u, u += 8, u < g && (h += E[i++] << u, u += 8)), v += h & (1 << g) - 1, v > s) { e.msg = "invalid distance too far back", A.mode = 30; break e } if (h >>>= g, u -= g, g = a - r, v > g) { if (g = v - g, g > d && A.sane) { e.msg = "invalid distance too far back", A.mode = 30; break e } if (x = 0, y = c, 0 === f) { if (x += l - g, g < p) { p -= g; do { R[a++] = c[x++] } while (--g); x = a - v, y = R } } else if (f < g) { if (x += l + f - g, g -= f, g < p) { p -= g; do { R[a++] = c[x++] } while (--g); if (x = 0, f < p) { g = f, p -= g; do { R[a++] = c[x++] } while (--g); x = a - v, y = R } } } else if (x += f - g, g < p) { p -= g; do { R[a++] = c[x++] } while (--g); x = a - v, y = R } for (; p > 2;)R[a++] = y[x++], R[a++] = y[x++], R[a++] = y[x++], p -= 3; p && (R[a++] = y[x++], p > 1 && (R[a++] = y[x++])) } else { x = a - v; do { R[a++] = R[x++], R[a++] = R[x++], R[a++] = R[x++], p -= 3 } while (p > 2); p && (R[a++] = R[x++], p > 1 && (R[a++] = R[x++])) } break } } break } } while (i < n && a < o); p = u >> 3, i -= p, u -= p << 3, h &= (1 << u) - 1, e.next_in = i, e.next_out = a, e.avail_in = i < n ? n - i + 5 : 5 - (i - n), e.avail_out = a < o ? o - a + 257 : 257 - (a - o), A.hold = h, A.bits = u }; const r = 15, o = new Uint16Array([3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0]), s = new Uint8Array([16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78]), l = new Uint16Array([1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0, 0]), d = new Uint8Array([16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, 29, 64, 64]); var f = (e, t, i, n, a, f, c, h) => { const u = h.bits; let w, b, k, m, _, g, p = 0, v = 0, x = 0, y = 0, E = 0, R = 0, A = 0, Z = 0, S = 0, T = 0, O = null, U = 0; const D = new Uint16Array(16), I = new Uint16Array(16); let B, N, C, z = null, F = 0; for (p = 0; p <= r; p++)D[p] = 0; for (v = 0; v < n; v++)D[t[i + v]]++; for (E = u, y = r; y >= 1 && 0 === D[y]; y--); if (E > y && (E = y), 0 === y) return a[f++] = 20971520, a[f++] = 20971520, h.bits = 1, 0; for (x = 1; x < y && 0 === D[x]; x++); for (E < x && (E = x), Z = 1, p = 1; p <= r; p++)if (Z <<= 1, Z -= D[p], Z < 0) return -1; if (Z > 0 && (0 === e || 1 !== y)) return -1; for (I[1] = 0, p = 1; p < r; p++)I[p + 1] = I[p] + D[p]; for (v = 0; v < n; v++)0 !== t[i + v] && (c[I[t[i + v]]++] = v); if (0 === e ? (O = z = c, g = 19) : 1 === e ? (O = o, U -= 257, z = s, F -= 257, g = 256) : (O = l, z = d, g = -1), T = 0, v = 0, p = x, _ = f, R = E, A = 0, k = -1, S = 1 << E, m = S - 1, 1 === e && S > 852 || 2 === e && S > 592) return 1; for (; ;) { B = p - A, c[v] < g ? (N = 0, C = c[v]) : c[v] > g ? (N = z[F + c[v]], C = O[U + c[v]]) : (N = 96, C = 0), w = 1 << p - A, b = 1 << R, x = b; do { b -= w, a[_ + (T >> A) + b] = B << 24 | N << 16 | C | 0 } while (0 !== b); for (w = 1 << p - 1; T & w;)w >>= 1; if (0 !== w ? (T &= w - 1, T += w) : T = 0, v++ , 0 == --D[p]) { if (p === y) break; p = t[i + c[v]] } if (p > E && (T & m) !== k) { for (0 === A && (A = E), _ += x, R = p - A, Z = 1 << R; R + A < y && (Z -= D[R + A], !(Z <= 0));)R++ , Z <<= 1; if (S += 1 << R, 1 === e && S > 852 || 2 === e && S > 592) return 1; k = T & m, a[k] = E << 24 | R << 16 | _ - f | 0 } } return 0 !== T && (a[_ + T] = p - A << 24 | 64 << 16 | 0), h.bits = E, 0 }, c = { Z_NO_FLUSH: 0, Z_PARTIAL_FLUSH: 1, Z_SYNC_FLUSH: 2, Z_FULL_FLUSH: 3, Z_FINISH: 4, Z_BLOCK: 5, Z_TREES: 6, Z_OK: 0, Z_STREAM_END: 1, Z_NEED_DICT: 2, Z_ERRNO: -1, Z_STREAM_ERROR: -2, Z_DATA_ERROR: -3, Z_MEM_ERROR: -4, Z_BUF_ERROR: -5, Z_NO_COMPRESSION: 0, Z_BEST_SPEED: 1, Z_BEST_COMPRESSION: 9, Z_DEFAULT_COMPRESSION: -1, Z_FILTERED: 1, Z_HUFFMAN_ONLY: 2, Z_RLE: 3, Z_FIXED: 4, Z_DEFAULT_STRATEGY: 0, Z_BINARY: 0, Z_TEXT: 1, Z_UNKNOWN: 2, Z_DEFLATED: 8 }; const { Z_FINISH: h, Z_BLOCK: u, Z_TREES: w, Z_OK: b, Z_STREAM_END: k, Z_NEED_DICT: m, Z_STREAM_ERROR: _, Z_DATA_ERROR: g, Z_MEM_ERROR: p, Z_BUF_ERROR: v, Z_DEFLATED: x } = c, y = 12, E = 30, R = e => (e >>> 24 & 255) + (e >>> 8 & 65280) + ((65280 & e) << 8) + ((255 & e) << 24); function A() { this.mode = 0, this.last = !1, this.wrap = 0, this.havedict = !1, this.flags = 0, this.dmax = 0, this.check = 0, this.total = 0, this.head = null, this.wbits = 0, this.wsize = 0, this.whave = 0, this.wnext = 0, this.window = null, this.hold = 0, this.bits = 0, this.length = 0, this.offset = 0, this.extra = 0, this.lencode = null, this.distcode = null, this.lenbits = 0, this.distbits = 0, this.ncode = 0, this.nlen = 0, this.ndist = 0, this.have = 0, this.next = null, this.lens = new Uint16Array(320), this.work = new Uint16Array(288), this.lendyn = null, this.distdyn = null, this.sane = 0, this.back = 0, this.was = 0 } const Z = e => { if (!e || !e.state) return _; const t = e.state; return e.total_in = e.total_out = t.total = 0, e.msg = "", t.wrap && (e.adler = 1 & t.wrap), t.mode = 1, t.last = 0, t.havedict = 0, t.dmax = 32768, t.head = null, t.hold = 0, t.bits = 0, t.lencode = t.lendyn = new Int32Array(852), t.distcode = t.distdyn = new Int32Array(592), t.sane = 1, t.back = -1, b }, S = e => { if (!e || !e.state) return _; const t = e.state; return t.wsize = 0, t.whave = 0, t.wnext = 0, Z(e) }, T = (e, t) => { let i; if (!e || !e.state) return _; const n = e.state; return t < 0 ? (i = 0, t = -t) : (i = 1 + (t >> 4), t < 48 && (t &= 15)), t && (t < 8 || t > 15) ? _ : (null !== n.window && n.wbits !== t && (n.window = null), n.wrap = i, n.wbits = t, S(e)) }, O = (e, t) => { if (!e) return _; const i = new A; e.state = i, i.window = null; const n = T(e, t); return n !== b && (e.state = null), n }; let U, D, I = !0; const B = e => { if (I) { U = new Int32Array(512), D = new Int32Array(32); let t = 0; for (; t < 144;)e.lens[t++] = 8; for (; t < 256;)e.lens[t++] = 9; for (; t < 280;)e.lens[t++] = 7; for (; t < 288;)e.lens[t++] = 8; for (f(1, e.lens, 0, 288, U, 0, e.work, { bits: 9 }), t = 0; t < 32;)e.lens[t++] = 5; f(2, e.lens, 0, 32, D, 0, e.work, { bits: 5 }), I = !1 } e.lencode = U, e.lenbits = 9, e.distcode = D, e.distbits = 5 }, N = (e, t, i, n) => { let a; const r = e.state; return null === r.window && (r.wsize = 1 << r.wbits, r.wnext = 0, r.whave = 0, r.window = new Uint8Array(r.wsize)), n >= r.wsize ? (r.window.set(t.subarray(i - r.wsize, i), 0), r.wnext = 0, r.whave = r.wsize) : (a = r.wsize - r.wnext, a > n && (a = n), r.window.set(t.subarray(i - n, i - n + a), r.wnext), (n -= a) ? (r.window.set(t.subarray(i - n, i), 0), r.wnext = n, r.whave = r.wsize) : (r.wnext += a, r.wnext === r.wsize && (r.wnext = 0), r.whave < r.wsize && (r.whave += a))), 0 }; var C = { inflateReset: S, inflateReset2: T, inflateResetKeep: Z, inflateInit: e => O(e, 15), inflateInit2: O, inflate: (e, i) => { let r, o, s, l, d, c, A, Z, S, T, O, U, D, I, C, z, F, L, M, H, j, K, P = 0; const Y = new Uint8Array(4); let G, X; const W = new Uint8Array([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]); if (!e || !e.state || !e.output || !e.input && 0 !== e.avail_in) return _; r = e.state, r.mode === y && (r.mode = 13), d = e.next_out, s = e.output, A = e.avail_out, l = e.next_in, o = e.input, c = e.avail_in, Z = r.hold, S = r.bits, T = c, O = A, K = b; e: for (; ;)switch (r.mode) { case 1: if (0 === r.wrap) { r.mode = 13; break } for (; S < 16;) { if (0 === c) break e; c-- , Z += o[l++] << S, S += 8 } if (2 & r.wrap && 35615 === Z) { r.check = 0, Y[0] = 255 & Z, Y[1] = Z >>> 8 & 255, r.check = n(r.check, Y, 2, 0), Z = 0, S = 0, r.mode = 2; break } if (r.flags = 0, r.head && (r.head.done = !1), !(1 & r.wrap) || (((255 & Z) << 8) + (Z >> 8)) % 31) { e.msg = "incorrect header check", r.mode = E; break } if ((15 & Z) !== x) { e.msg = "unknown compression method", r.mode = E; break } if (Z >>>= 4, S -= 4, j = 8 + (15 & Z), 0 === r.wbits) r.wbits = j; else if (j > r.wbits) { e.msg = "invalid window size", r.mode = E; break } r.dmax = 1 << r.wbits, e.adler = r.check = 1, r.mode = 512 & Z ? 10 : y, Z = 0, S = 0; break; case 2: for (; S < 16;) { if (0 === c) break e; c-- , Z += o[l++] << S, S += 8 } if (r.flags = Z, (255 & r.flags) !== x) { e.msg = "unknown compression method", r.mode = E; break } if (57344 & r.flags) { e.msg = "unknown header flags set", r.mode = E; break } r.head && (r.head.text = Z >> 8 & 1), 512 & r.flags && (Y[0] = 255 & Z, Y[1] = Z >>> 8 & 255, r.check = n(r.check, Y, 2, 0)), Z = 0, S = 0, r.mode = 3; case 3: for (; S < 32;) { if (0 === c) break e; c-- , Z += o[l++] << S, S += 8 } r.head && (r.head.time = Z), 512 & r.flags && (Y[0] = 255 & Z, Y[1] = Z >>> 8 & 255, Y[2] = Z >>> 16 & 255, Y[3] = Z >>> 24 & 255, r.check = n(r.check, Y, 4, 0)), Z = 0, S = 0, r.mode = 4; case 4: for (; S < 16;) { if (0 === c) break e; c-- , Z += o[l++] << S, S += 8 } r.head && (r.head.xflags = 255 & Z, r.head.os = Z >> 8), 512 & r.flags && (Y[0] = 255 & Z, Y[1] = Z >>> 8 & 255, r.check = n(r.check, Y, 2, 0)), Z = 0, S = 0, r.mode = 5; case 5: if (1024 & r.flags) { for (; S < 16;) { if (0 === c) break e; c-- , Z += o[l++] << S, S += 8 } r.length = Z, r.head && (r.head.extra_len = Z), 512 & r.flags && (Y[0] = 255 & Z, Y[1] = Z >>> 8 & 255, r.check = n(r.check, Y, 2, 0)), Z = 0, S = 0 } else r.head && (r.head.extra = null); r.mode = 6; case 6: if (1024 & r.flags && (U = r.length, U > c && (U = c), U && (r.head && (j = r.head.extra_len - r.length, r.head.extra || (r.head.extra = new Uint8Array(r.head.extra_len)), r.head.extra.set(o.subarray(l, l + U), j)), 512 & r.flags && (r.check = n(r.check, o, U, l)), c -= U, l += U, r.length -= U), r.length)) break e; r.length = 0, r.mode = 7; case 7: if (2048 & r.flags) { if (0 === c) break e; U = 0; do { j = o[l + U++], r.head && j && r.length < 65536 && (r.head.name += String.fromCharCode(j)) } while (j && U < c); if (512 & r.flags && (r.check = n(r.check, o, U, l)), c -= U, l += U, j) break e } else r.head && (r.head.name = null); r.length = 0, r.mode = 8; case 8: if (4096 & r.flags) { if (0 === c) break e; U = 0; do { j = o[l + U++], r.head && j && r.length < 65536 && (r.head.comment += String.fromCharCode(j)) } while (j && U < c); if (512 & r.flags && (r.check = n(r.check, o, U, l)), c -= U, l += U, j) break e } else r.head && (r.head.comment = null); r.mode = 9; case 9: if (512 & r.flags) { for (; S < 16;) { if (0 === c) break e; c-- , Z += o[l++] << S, S += 8 } if (Z !== (65535 & r.check)) { e.msg = "header crc mismatch", r.mode = E; break } Z = 0, S = 0 } r.head && (r.head.hcrc = r.flags >> 9 & 1, r.head.done = !0), e.adler = r.check = 0, r.mode = y; break; case 10: for (; S < 32;) { if (0 === c) break e; c-- , Z += o[l++] << S, S += 8 } e.adler = r.check = R(Z), Z = 0, S = 0, r.mode = 11; case 11: if (0 === r.havedict) return e.next_out = d, e.avail_out = A, e.next_in = l, e.avail_in = c, r.hold = Z, r.bits = S, m; e.adler = r.check = 1, r.mode = y; case y: if (i === u || i === w) break e; case 13: if (r.last) { Z >>>= 7 & S, S -= 7 & S, r.mode = 27; break } for (; S < 3;) { if (0 === c) break e; c-- , Z += o[l++] << S, S += 8 } switch (r.last = 1 & Z, Z >>>= 1, S -= 1, 3 & Z) { case 0: r.mode = 14; break; case 1: if (B(r), r.mode = 20, i === w) { Z >>>= 2, S -= 2; break e } break; case 2: r.mode = 17; break; case 3: e.msg = "invalid block type", r.mode = E }Z >>>= 2, S -= 2; break; case 14: for (Z >>>= 7 & S, S -= 7 & S; S < 32;) { if (0 === c) break e; c-- , Z += o[l++] << S, S += 8 } if ((65535 & Z) != (Z >>> 16 ^ 65535)) { e.msg = "invalid stored block lengths", r.mode = E; break } if (r.length = 65535 & Z, Z = 0, S = 0, r.mode = 15, i === w) break e; case 15: r.mode = 16; case 16: if (U = r.length, U) { if (U > c && (U = c), U > A && (U = A), 0 === U) break e; s.set(o.subarray(l, l + U), d), c -= U, l += U, A -= U, d += U, r.length -= U; break } r.mode = y; break; case 17: for (; S < 14;) { if (0 === c) break e; c-- , Z += o[l++] << S, S += 8 } if (r.nlen = 257 + (31 & Z), Z >>>= 5, S -= 5, r.ndist = 1 + (31 & Z), Z >>>= 5, S -= 5, r.ncode = 4 + (15 & Z), Z >>>= 4, S -= 4, r.nlen > 286 || r.ndist > 30) { e.msg = "too many length or distance symbols", r.mode = E; break } r.have = 0, r.mode = 18; case 18: for (; r.have < r.ncode;) { for (; S < 3;) { if (0 === c) break e; c-- , Z += o[l++] << S, S += 8 } r.lens[W[r.have++]] = 7 & Z, Z >>>= 3, S -= 3 } for (; r.have < 19;)r.lens[W[r.have++]] = 0; if (r.lencode = r.lendyn, r.lenbits = 7, G = { bits: r.lenbits }, K = f(0, r.lens, 0, 19, r.lencode, 0, r.work, G), r.lenbits = G.bits, K) { e.msg = "invalid code lengths set", r.mode = E; break } r.have = 0, r.mode = 19; case 19: for (; r.have < r.nlen + r.ndist;) { for (; P = r.lencode[Z & (1 << r.lenbits) - 1], C = P >>> 24, z = P >>> 16 & 255, F = 65535 & P, !(C <= S);) { if (0 === c) break e; c-- , Z += o[l++] << S, S += 8 } if (F < 16) Z >>>= C, S -= C, r.lens[r.have++] = F; else { if (16 === F) { for (X = C + 2; S < X;) { if (0 === c) break e; c-- , Z += o[l++] << S, S += 8 } if (Z >>>= C, S -= C, 0 === r.have) { e.msg = "invalid bit length repeat", r.mode = E; break } j = r.lens[r.have - 1], U = 3 + (3 & Z), Z >>>= 2, S -= 2 } else if (17 === F) { for (X = C + 3; S < X;) { if (0 === c) break e; c-- , Z += o[l++] << S, S += 8 } Z >>>= C, S -= C, j = 0, U = 3 + (7 & Z), Z >>>= 3, S -= 3 } else { for (X = C + 7; S < X;) { if (0 === c) break e; c-- , Z += o[l++] << S, S += 8 } Z >>>= C, S -= C, j = 0, U = 11 + (127 & Z), Z >>>= 7, S -= 7 } if (r.have + U > r.nlen + r.ndist) { e.msg = "invalid bit length repeat", r.mode = E; break } for (; U--;)r.lens[r.have++] = j } } if (r.mode === E) break; if (0 === r.lens[256]) { e.msg = "invalid code -- missing end-of-block", r.mode = E; break } if (r.lenbits = 9, G = { bits: r.lenbits }, K = f(1, r.lens, 0, r.nlen, r.lencode, 0, r.work, G), r.lenbits = G.bits, K) { e.msg = "invalid literal/lengths set", r.mode = E; break } if (r.distbits = 6, r.distcode = r.distdyn, G = { bits: r.distbits }, K = f(2, r.lens, r.nlen, r.ndist, r.distcode, 0, r.work, G), r.distbits = G.bits, K) { e.msg = "invalid distances set", r.mode = E; break } if (r.mode = 20, i === w) break e; case 20: r.mode = 21; case 21: if (c >= 6 && A >= 258) { e.next_out = d, e.avail_out = A, e.next_in = l, e.avail_in = c, r.hold = Z, r.bits = S, a(e, O), d = e.next_out, s = e.output, A = e.avail_out, l = e.next_in, o = e.input, c = e.avail_in, Z = r.hold, S = r.bits, r.mode === y && (r.back = -1); break } for (r.back = 0; P = r.lencode[Z & (1 << r.lenbits) - 1], C = P >>> 24, z = P >>> 16 & 255, F = 65535 & P, !(C <= S);) { if (0 === c) break e; c-- , Z += o[l++] << S, S += 8 } if (z && 0 == (240 & z)) { for (L = C, M = z, H = F; P = r.lencode[H + ((Z & (1 << L + M) - 1) >> L)], C = P >>> 24, z = P >>> 16 & 255, F = 65535 & P, !(L + C <= S);) { if (0 === c) break e; c-- , Z += o[l++] << S, S += 8 } Z >>>= L, S -= L, r.back += L } if (Z >>>= C, S -= C, r.back += C, r.length = F, 0 === z) { r.mode = 26; break } if (32 & z) { r.back = -1, r.mode = y; break } if (64 & z) { e.msg = "invalid literal/length code", r.mode = E; break } r.extra = 15 & z, r.mode = 22; case 22: if (r.extra) { for (X = r.extra; S < X;) { if (0 === c) break e; c-- , Z += o[l++] << S, S += 8 } r.length += Z & (1 << r.extra) - 1, Z >>>= r.extra, S -= r.extra, r.back += r.extra } r.was = r.length, r.mode = 23; case 23: for (; P = r.distcode[Z & (1 << r.distbits) - 1], C = P >>> 24, z = P >>> 16 & 255, F = 65535 & P, !(C <= S);) { if (0 === c) break e; c-- , Z += o[l++] << S, S += 8 } if (0 == (240 & z)) { for (L = C, M = z, H = F; P = r.distcode[H + ((Z & (1 << L + M) - 1) >> L)], C = P >>> 24, z = P >>> 16 & 255, F = 65535 & P, !(L + C <= S);) { if (0 === c) break e; c-- , Z += o[l++] << S, S += 8 } Z >>>= L, S -= L, r.back += L } if (Z >>>= C, S -= C, r.back += C, 64 & z) { e.msg = "invalid distance code", r.mode = E; break } r.offset = F, r.extra = 15 & z, r.mode = 24; case 24: if (r.extra) { for (X = r.extra; S < X;) { if (0 === c) break e; c-- , Z += o[l++] << S, S += 8 } r.offset += Z & (1 << r.extra) - 1, Z >>>= r.extra, S -= r.extra, r.back += r.extra } if (r.offset > r.dmax) { e.msg = "invalid distance too far back", r.mode = E; break } r.mode = 25; case 25: if (0 === A) break e; if (U = O - A, r.offset > U) { if (U = r.offset - U, U > r.whave && r.sane) { e.msg = "invalid distance too far back", r.mode = E; break } U > r.wnext ? (U -= r.wnext, D = r.wsize - U) : D = r.wnext - U, U > r.length && (U = r.length), I = r.window } else I = s, D = d - r.offset, U = r.length; U > A && (U = A), A -= U, r.length -= U; do { s[d++] = I[D++] } while (--U); 0 === r.length && (r.mode = 21); break; case 26: if (0 === A) break e; s[d++] = r.length, A-- , r.mode = 21; break; case 27: if (r.wrap) { for (; S < 32;) { if (0 === c) break e; c-- , Z |= o[l++] << S, S += 8 } if (O -= A, e.total_out += O, r.total += O, O && (e.adler = r.check = r.flags ? n(r.check, s, O, d - O) : t(r.check, s, O, d - O)), O = A, (r.flags ? Z : R(Z)) !== r.check) { e.msg = "incorrect data check", r.mode = E; break } Z = 0, S = 0 } r.mode = 28; case 28: if (r.wrap && r.flags) { for (; S < 32;) { if (0 === c) break e; c-- , Z += o[l++] << S, S += 8 } if (Z !== (4294967295 & r.total)) { e.msg = "incorrect length check", r.mode = E; break } Z = 0, S = 0 } r.mode = 29; case 29: K = k; break e; case E: K = g; break e; case 31: return p; case 32: default: return _ }return e.next_out = d, e.avail_out = A, e.next_in = l, e.avail_in = c, r.hold = Z, r.bits = S, (r.wsize || O !== e.avail_out && r.mode < E && (r.mode < 27 || i !== h)) && N(e, e.output, e.next_out, O - e.avail_out), T -= e.avail_in, O -= e.avail_out, e.total_in += T, e.total_out += O, r.total += O, r.wrap && O && (e.adler = r.check = r.flags ? n(r.check, s, O, e.next_out - O) : t(r.check, s, O, e.next_out - O)), e.data_type = r.bits + (r.last ? 64 : 0) + (r.mode === y ? 128 : 0) + (20 === r.mode || 15 === r.mode ? 256 : 0), (0 === T && 0 === O || i === h) && K === b && (K = v), K }, inflateEnd: e => { if (!e || !e.state) return _; let t = e.state; return t.window && (t.window = null), e.state = null, b }, inflateGetHeader: (e, t) => { if (!e || !e.state) return _; const i = e.state; return 0 == (2 & i.wrap) ? _ : (i.head = t, t.done = !1, b) }, inflateSetDictionary: (e, i) => { const n = i.length; let a, r, o; return e && e.state ? (a = e.state, 0 !== a.wrap && 11 !== a.mode ? _ : 11 === a.mode && (r = 1, r = t(r, i, n, 0), r !== a.check) ? g : (o = N(e, i, n, n), o ? (a.mode = 31, p) : (a.havedict = 1, b))) : _ }, inflateInfo: "pako inflate (from Nodeca project)" }; const z = (e, t) => Object.prototype.hasOwnProperty.call(e, t); var F = function (e) { const t = Array.prototype.slice.call(arguments, 1); for (; t.length;) { const i = t.shift(); if (i) { if ("object" != typeof i) throw new TypeError(i + "must be non-object"); for (const t in i) z(i, t) && (e[t] = i[t]) } } return e }, L = e => { let t = 0; for (let i = 0, n = e.length; i < n; i++)t += e[i].length; const i = new Uint8Array(t); for (let t = 0, n = 0, a = e.length; t < a; t++) { let a = e[t]; i.set(a, n), n += a.length } return i }; let M = !0; try { String.fromCharCode.apply(null, new Uint8Array(1)) } catch (e) { M = !1 } const H = new Uint8Array(256); for (let e = 0; e < 256; e++)H[e] = e >= 252 ? 6 : e >= 248 ? 5 : e >= 240 ? 4 : e >= 224 ? 3 : e >= 192 ? 2 : 1; H[254] = H[254] = 1; var j = e => { if ("function" == typeof TextEncoder && TextEncoder.prototype.encode) return (new TextEncoder).encode(e); let t, i, n, a, r, o = e.length, s = 0; for (a = 0; a < o; a++)i = e.charCodeAt(a), 55296 == (64512 & i) && a + 1 < o && (n = e.charCodeAt(a + 1), 56320 == (64512 & n) && (i = 65536 + (i - 55296 << 10) + (n - 56320), a++)), s += i < 128 ? 1 : i < 2048 ? 2 : i < 65536 ? 3 : 4; for (t = new Uint8Array(s), r = 0, a = 0; r < s; a++)i = e.charCodeAt(a), 55296 == (64512 & i) && a + 1 < o && (n = e.charCodeAt(a + 1), 56320 == (64512 & n) && (i = 65536 + (i - 55296 << 10) + (n - 56320), a++)), i < 128 ? t[r++] = i : i < 2048 ? (t[r++] = 192 | i >>> 6, t[r++] = 128 | 63 & i) : i < 65536 ? (t[r++] = 224 | i >>> 12, t[r++] = 128 | i >>> 6 & 63, t[r++] = 128 | 63 & i) : (t[r++] = 240 | i >>> 18, t[r++] = 128 | i >>> 12 & 63, t[r++] = 128 | i >>> 6 & 63, t[r++] = 128 | 63 & i); return t }, K = (e, t) => { const i = t || e.length; if ("function" == typeof TextDecoder && TextDecoder.prototype.decode) return (new TextDecoder).decode(e.subarray(0, t)); let n, a; const r = new Array(2 * i); for (a = 0, n = 0; n < i;) { let t = e[n++]; if (t < 128) { r[a++] = t; continue } let o = H[t]; if (o > 4) r[a++] = 65533, n += o - 1; else { for (t &= 2 === o ? 31 : 3 === o ? 15 : 7; o > 1 && n < i;)t = t << 6 | 63 & e[n++], o--; o > 1 ? r[a++] = 65533 : t < 65536 ? r[a++] = t : (t -= 65536, r[a++] = 55296 | t >> 10 & 1023, r[a++] = 56320 | 1023 & t) } } return ((e, t) => { if (t < 65534 && e.subarray && M) return String.fromCharCode.apply(null, e.length === t ? e : e.subarray(0, t)); let i = ""; for (let n = 0; n < t; n++)i += String.fromCharCode(e[n]); return i })(r, a) }, P = (e, t) => { (t = t || e.length) > e.length && (t = e.length); let i = t - 1; for (; i >= 0 && 128 == (192 & e[i]);)i--; return i < 0 || 0 === i ? t : i + H[e[i]] > t ? i : t }, Y = { 2: "need dictionary", 1: "stream end", 0: "", "-1": "file error", "-2": "stream error", "-3": "data error", "-4": "insufficient memory", "-5": "buffer error", "-6": "incompatible version" }; var G = function () { this.input = null, this.next_in = 0, this.avail_in = 0, this.total_in = 0, this.output = null, this.next_out = 0, this.avail_out = 0, this.total_out = 0, this.msg = "", this.state = null, this.data_type = 2, this.adler = 0 }; var X = function () { this.text = 0, this.time = 0, this.xflags = 0, this.os = 0, this.extra = null, this.extra_len = 0, this.name = "", this.comment = "", this.hcrc = 0, this.done = !1 }; const W = Object.prototype.toString, { Z_NO_FLUSH: q, Z_FINISH: J, Z_OK: Q, Z_STREAM_END: V, Z_NEED_DICT: $, Z_STREAM_ERROR: ee, Z_DATA_ERROR: te, Z_MEM_ERROR: ie } = c; function ne(e) { this.options = F({ chunkSize: 65536, windowBits: 15, to: "" }, e || {}); const t = this.options; t.raw && t.windowBits >= 0 && t.windowBits < 16 && (t.windowBits = -t.windowBits, 0 === t.windowBits && (t.windowBits = -15)), !(t.windowBits >= 0 && t.windowBits < 16) || e && e.windowBits || (t.windowBits += 32), t.windowBits > 15 && t.windowBits < 48 && 0 == (15 & t.windowBits) && (t.windowBits |= 15), this.err = 0, this.msg = "", this.ended = !1, this.chunks = [], this.strm = new G, this.strm.avail_out = 0; let i = C.inflateInit2(this.strm, t.windowBits); if (i !== Q) throw new Error(Y[i]); if (this.header = new X, C.inflateGetHeader(this.strm, this.header), t.dictionary && ("string" == typeof t.dictionary ? t.dictionary = j(t.dictionary) : "[object ArrayBuffer]" === W.call(t.dictionary) && (t.dictionary = new Uint8Array(t.dictionary)), t.raw && (i = C.inflateSetDictionary(this.strm, t.dictionary), i !== Q))) throw new Error(Y[i]) } function ae(e, t) { const i = new ne(t); if (i.push(e), i.err) throw i.msg || Y[i.err]; return i.result } ne.prototype.push = function (e, t) { const i = this.strm, n = this.options.chunkSize, a = this.options.dictionary; let r, o, s; if (this.ended) return !1; for (o = t === ~~t ? t : !0 === t ? J : q, "[object ArrayBuffer]" === W.call(e) ? i.input = new Uint8Array(e) : i.input = e, i.next_in = 0, i.avail_in = i.input.length; ;) { for (0 === i.avail_out && (i.output = new Uint8Array(n), i.next_out = 0, i.avail_out = n), r = C.inflate(i, o), r === $ && a && (r = C.inflateSetDictionary(i, a), r === Q ? r = C.inflate(i, o) : r === te && (r = $)); i.avail_in > 0 && r === V && i.state.wrap > 0 && 0 !== e[i.next_in];)C.inflateReset(i), r = C.inflate(i, o); switch (r) { case ee: case te: case $: case ie: return this.onEnd(r), this.ended = !0, !1 }if (s = i.avail_out, i.next_out && (0 === i.avail_out || r === V)) if ("string" === this.options.to) { let e = P(i.output, i.next_out), t = i.next_out - e, a = K(i.output, e); i.next_out = t, i.avail_out = n - t, t && i.output.set(i.output.subarray(e, e + t), 0), this.onData(a) } else this.onData(i.output.length === i.next_out ? i.output : i.output.subarray(0, i.next_out)); if (r !== Q || 0 !== s) { if (r === V) return r = C.inflateEnd(this.strm), this.onEnd(r), this.ended = !0, !0; if (0 === i.avail_in) break } } return !0 }, ne.prototype.onData = function (e) { this.chunks.push(e) }, ne.prototype.onEnd = function (e) { e === Q && ("string" === this.options.to ? this.result = this.chunks.join("") : this.result = L(this.chunks)), this.chunks = [], this.err = e, this.msg = this.strm.msg }; var re = ne, oe = ae, se = function (e, t) { return (t = t || {}).raw = !0, ae(e, t) }, le = ae, de = c, fe = { Inflate: re, inflate: oe, inflateRaw: se, ungzip: le, constants: de }; e.Inflate = re, e.constants = de, e.default = fe, e.inflate = oe, e.inflateRaw = se, e.ungzip = le, Object.defineProperty(e, "__esModule", { value: !0 }) }));

    function roott(e, t) {
        for (var i = "", n = 0; n < e.length; n++) {
            var a = e.charCodeAt(n)
                , o = a;
            a >= 65 && a <= 90 && (o = (a - 65 - 1 * t + 26) % 26 + 65),
                a >= 97 && a <= 122 && (o = (a - 97 - 1 * t + 26) % 26 + 97),
                i += String.fromCharCode(o)
        }
        return i
    }

    function pushmsg(e) {
        let t = "";
        if ("undefined" == typeof window)
            try {
                t = nodeAtob(e)
            } catch (e) {
            }
        else
            t = atob(e);
        const i = t.split("").map(function (e) {
            return e.charCodeAt(0)
        })
            , n = new Uint8Array(i)
            , a = window.pako.inflate(n);
        return t = function (e) {
            let t, i, n, a, o = "";
            const s = e.length;
            for (t = 0; t < s;)
                switch ((i = e[t++]) >> 4) {
                    case 0:
                    case 1:
                    case 2:
                    case 3:
                    case 4:
                    case 5:
                    case 6:
                    case 7:
                        o += String.fromCharCode(i);
                        break;
                    case 12:
                    case 13:
                        n = e[t++],
                            o += String.fromCharCode((31 & i) << 6 | 63 & n);
                        break;
                    case 14:
                        n = e[t++],
                            a = e[t++],
                            o += String.fromCharCode((15 & i) << 12 | (63 & n) << 6 | (63 & a) << 0)
                }
            return o
        }(new Uint16Array(a)),
            unescape(t)
    }

    function main(e,t) {

        var i = pushmsg(roott(e, t));
        return JSON.parse(i);
    }

    #Code#
})();

经过分析,订阅的四个主题分别代表为:

chatroom_v2/R1_{matchId} 订阅-聊天

live/football/detail/{matchId}/data_v2 订阅-比赛趋势

live/football/detail/{matchId}/bet365 订阅-bet365指数

live/football/detail/{matchId}/top_v2 订阅-比赛数据

最后附上懒人链接【C#WinForm雷速比赛MQTT逆向程序




总结

文章到这里就结束了,在js逆向中千方百试,要合理运用以及推理分析需要不断的去尝试,好记性不如烂笔头。

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
您好!我可以为您提供关于使用 C# WinForms 编写 MQTT 客户端的一些指导。 首先,您需要为您项目添加 MQTT 客户端库。有几个流行的库可供选择,比如 M2MqttMQTTnet 和 uPLibrary。您可以根据自己的需要选择其中一个库进行安装。 接下来,您需要在您的 WinForms 应用程序中创建一个 MQTT 客户端。首先,您需要导入所选 MQTT 客户端库的命名空间。然后,创建一个 MQTT 客户端实例,并设置连接到 MQTT 代理所需的参数,如代理地址、端口和客户端ID。 下面是一个简单的示例代码,展示了如何使用 M2Mqtt 库创建一个基本的 MQTT 客户端: ```csharp using System; using System.Windows.Forms; using uPLibrary.Networking.M2Mqtt; using uPLibrary.Networking.M2Mqtt.Messages; namespace YourNamespace { public partial class MainForm : Form { private MqttClient mqttClient; public MainForm() { InitializeComponent(); // 创建 MQTT 客户端实例 mqttClient = new MqttClient("mqtt.broker.com"); // 设置回调函数 mqttClient.MqttMsgPublishReceived += MqttClient_MqttMsgPublishReceived; } // 连接到 MQTT 代理 private void ConnectToMqttBroker() { string clientId = Guid.NewGuid().ToString(); mqttClient.Connect(clientId); } // 订阅主题 private void SubscribeToTopic(string topic) { mqttClient.Subscribe(new string[] { topic }, new byte[] { MqttMsgBase.QOS_LEVEL_AT_LEAST_ONCE }); } // 接收到消息的回调函数 private void MqttClient_MqttMsgPublishReceived(object sender, MqttMsgPublishEventArgs e) { string message = System.Text.Encoding.UTF8.GetString(e.Message); // 在这里处理接收到的消息 // ... } // 发布消息 private void PublishMessage(string topic, string message) { mqttClient.Publish(topic, System.Text.Encoding.UTF8.GetBytes(message), MqttMsgBase.QOS_LEVEL_AT_LEAST_ONCE, false); } // 断开与 MQTT 代理的连接 private void DisconnectFromMqttBroker() { mqttClient.Disconnect(); } private void btnConnect_Click(object sender, EventArgs e) { ConnectToMqttBroker(); } private void btnSubscribe_Click(object sender, EventArgs e) { string topic = txtTopic.Text; SubscribeToTopic(topic); } private void btnPublish_Click(object sender, EventArgs e) { string topic = txtTopic.Text; string message = txtMessage.Text; PublishMessage(topic, message); } private void btnDisconnect_Click(object sender, EventArgs e) { DisconnectFromMqttBroker(); } } } ``` 上述代码创建了一个包含连接、订阅、发布和断开连接功能的 WinForms 窗体应用程序。您可以在窗体上放置一些控件,比如按钮和文本框,然后将相应的事件处理程序与这些控件关联。 请注意,这只是一个简单的示例,您可能需要根据您的具体需求进行相应的更改和扩展。 希望这能帮到您!如果您有任何其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奥特曼洗澡中

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值