封包协议,加密解密,线程发包,recv,明文收包

一.封包,协议

什么是封包?封包就是按照某种协议组成的一段字节集.这个很容易理解

那为什么要按照某种协议呢?这个我们一会再说

所有联网的软件,包括网络游戏,客户端和服务端进行通信都是通过封包进行的.

大家可以想象成2个人打电话,封包就是说话的内容.

而协议呢?就是封包的语种, 2个人打电话一个用日语,一个用英文

もしもし、韓さんです。 那边接电话 What are you talking? 这怎么交流啊?

好,那么我们定好协议,本通电话我们全部采用中文,这样就可以正常交流了对吧?

这就是协议

一句话概括, 联网软件,客户端和服务端通过约定好的协议用封包来进行通信.

二.收发包

上面了解了什么是封包协议,我们来看下什么是收发包

网络游戏和单机游戏的区别就在于时刻要和服务器进行通信,也就是收发包.我们发给服务器的叫做发包, 我们收到服务器发给我们的叫做收包,

当然这是相当于我们而言.反之一样.

来一个简单的例子:

我们进入游戏说一句:你好! 为什么别的玩家可以看到?

通过这个流程我们就会了解收发包以及服务器的作用.

首先我们发送要和周围人说你好的封包给服务器,

服务器收到我们的封包以后, 一般会给我们回一个封包,说OK没问题,当然也可能高冷的不回啊.

然后服务器会执行代码判断我周围都有哪些玩家,然后对这些玩家逐个发包

这些玩家收到服务器的封包以后,就会看到我们的喊话内容了.

了解了什么是收发包

我们就要知道调用什么函数来实现.

这里的三大快递公司

就是三个发包函数 send sendto WSASend 以及对应的 3个收包函数 recv recvfrom WSARecv

用哪一组都是可以的.

例如 口袋西游是 send recv

幻想神域是 WSASend WSARecv

三.心跳包

上面了解了封包协议,收发包,我们来看下什么是心跳包?

心跳包也是封包,只是一种比较特殊的封包.

他可以证明我们的客户端还活着,顾名思义,有心跳就是活着.

举个简单的例子:

2个人打电话, A: 喂在吗? B:... A:在吗? B....A:挂了. 多次没得到回应直接就挂断了.

心跳包也一样,当服务器给我们发包,我们没有回复的时候,服务器就有可能把我们强制踢下线. 当然也有可能我们定时主动发送

1-2次服务器可能会认为偶尔丢包,如果2个以上应该就会直接认为我们掉线,直接强制踢下线了.

心跳包有很多好处:

1.一定程度防止脱机外挂

2.掉线立刻下线, 比如以前的老游戏,电脑蓝屏关机了,由于没有自然下线, 心跳也特别少,人物可能还在上面很长一段时间,这是不安全的,比如传奇2

3.可以隐藏一些数据在心跳包中.

四.针对发包函数的保护方法

了解了封包的概念,我们发现发包函数非常重要,

监听了发包函数就相当于掌握了所有功能实现的方法,可以替代所有call.监听了收包函数就相当于掌握了所以数据信息.因为数据本质来源于服务器

同时通过调用关系我们还知道,可以通过发包函数返回到所有的功能函数.

这是连新手的都知道的2点,所以一定要进行防护

针对这2点我们可以做什么防护呢?

那么第一件事,当然是把这个函数给尽量的隐藏起来

有的时候我们会发现,三大发包函数都不断!

那么是不是这个游戏不用这3个函数发包呢?当然不是,我们只能用这三个发包函数发包,没有其他的方法。

那就是重新实现发包函数,自己重新写一个发包函数,不用系统提供的成品函数了。

说的简单点就是,代码还是这三个发包函数的代码,只是换了个位置,搬家了,不直接调用这3个函数了。

知道这个原理以后就很容易处理了

方法一

既然还是之前的代码,那么我们到他的更内层函数下断就可以了

既WSPSend,相当于用内层函数当特征码。

函数体内的虚函数

X64结构不一样,但是一样是虚函数进去 找到WSPSend

方法二

通过三个函数的特征码到对应模块中进行搜索,这里注意的是这个重新实现的发包函数,可能在原来的模块中ws2_32,也可能在游戏自己的某个模块中。

所以搜索的时候要注意.

特征码比如头部这一段

可能会有微小变化,所以我们要尝试

同时也可能搜索到多个位置,全部下断,测试即可.

总之两种方法思路是一致的,都是找原代码的特征而已。

对发包函数第二个保护方案就是加密

如果不加密,直接监听发包函数就可以获得所有的信息了.

比如直接hook send函数

例如WPE等简单工具就是hook的发包函数

所以必须进行加密

加密也一定要进行动态加密,就是同样的结果加密后内容是不同的.那么以上方法也就没有任何作用了.

五.对发包函数第三个保护方案就是线程发包

正常的游戏调用流程是

功能函数1---》功能函数2---》组包过程---》明文封包---》加密---》发包函数

所谓组包过程就是把功能函数的参数按照格式组成一个字节集

明文封包就是组装完成但是没有进行加密的封包

最后一步加密发送.

通过上面的调用关系,我们能看出来

这样只要找到发包函数,所以功能函数以及加密解密函数随意返回,随意分析

那怎么样避免呢?线程发包!

线程发包原理

就是说用2条线程来控制以上流程,让逆向者不能直接返回

第一条线程

功能函数1---》功能函数2---》组包---》明文封包---》封包写到某一个地址中

第二条线程

得到那个地址中的内容---》加密---》发包函数

当然两条线程分工不一定是严格这样写的,也可能明文封包在第二条线程中

这样的发包会有什么的特征呢?

1.无论什么功能,堆栈返回都是一样的,因为第二条线程的调用过程,什么功能都一样了

2.第二条线程由于不断循环,断的可能会比较频繁

六.线程发包跳出循环线程

先不管线程通信都有哪些方法,我们先来一个锻炼,然后我们再详细分析原理.

上面我们知道了线程发包的原理,那么这2条线程唯一的关联就是封包内容

所以我们直接追封包内容来源即可.就有线索从第二条线程返回到第一条线程了.

1.包内容地址如果是固定的,我们直接下断就能跳出去了,相当于用全局变量做线程之间通信

这里是变化的

那么我们追其来源

edi+2888 开始不发生变化了

根据我们的思路,要找变化的值来源, 那么我们在该地址上下写入断,

找到写入来源以后 我们看是否跳出了线程

发现并未跳出线程,还在原来的线程里.

2.那么我们还要继续追其来源

edx开始不变化了,对EDX下断

跳出了线程,我们可以通过这里下断,断到功能函数了

返回就是明文发包call

而这里就可以当成我们的"send"了

七.整理完全线程发包流程

1.跳出线程在这个位置,我们来整理下整体流程

线程1将ebp写入一个地址.

ebp 是会变的是动态申请的,存放的封包结构

这个地址是全局类对象指向封包结构的指针

线程2将线程1写入的 封包结构取出来

edi+2880就是全局类对象指向封包结构的指针

取出来给edx,我们依然写ebp 为了知道是线程1断出去的ebp

[[ebp]+8] 又写入 全局类型对象 +2888的位置

全局类对象+2888的位置 取出来给ebx

取出来的就是前面的 [[ebp]+8]

[[ebp+8]+4] 就是是封包内容

八.验证发包内容

我们下断可以验证一下 里面的封包内容是否是最后发送的内容,这个必须验证,防止中间又有多次加密

如果不相同,我们还要在这里对封包下访问断,追踪他经历了什么

方法很简单:

我们同时在这个位置下断 和WSASend 下断

对比两个位置封包是否一样

分别下条件断

byte ptr[[[EBP+8]+4]] != 0x0F

喊话11对比

2B771F00 C2C70011

2B771F04 C0105E58

2B771F08 D805FBB8

2B771F0C 910B1805

2B771F10 6F043A08

$ ==> >C2C70011

$+4 >C0105E58

$+8 >D805FBB8

$+C >910B1805

$+10 >6F043A08

$+14 >404B0000

发现是一样的那么我们在这里继续分析即可

九.明文发包call 和锁定加密call

我们现在跳出的位置 其实就是相当于"send"

正常可以返回到各种功能call, 如果使用call的话,我们现在已经足够了

如果想自己加密自己send发送封包,那么我们还需要分析加密解密过程,分析加密算法.

返回一层层看看 是否有明文内容,喊话是最容易看到明文的位置

返回的第一个call里面就看到了明文

那么说明 从这个call的头部 到第二次跳出的位置 中间就有加密call

如果这层发现不了明文,我们继续返回

当然我们也可以从ebp 一句一句逆向来源也是可以的

这个地方同样需要条件断 byte ptr[[[esp]+4]]!=0f

那么这地方就是明文发包call

找到了明文发包call,我们可以直接调用或则调用更外层功能函数实现各种功能,这之前我们都已经讲过了,这节课我们想用更好的方法

就是找到加密函数,我们自己加密封包,然后通过发包函数发送

十.加密call

加密函数的位置我们已经锁定了,就是在返回的call到我们断的位置之间

那么可以进行分析了

从call 断下以后 F7单独执行即可

发现了加密calll

以及分析出来加密call的参数

00B92266 2BF8 sub edi, eax

00B92268 83C5 02 add ebp, 2

00B9226B 55 push ebp ; 加密的地址

00B9226C 83C3 02 add ebx, 2

00B9226F 53 push ebx ; 加密的地址

00B92270 83C7 FE add edi, -2

00B92273 8D46 54 lea eax, dword ptr [esi+54]

00B92276 57 push edi ; 加密的长度

00B92277 50 push eax ; 秘钥 往上追 [[[[[00f84ba4]]+4]+0xC+8]]+54

00B92278 E8 83240000 call 00B94700 ; 加密call

00B9227D 8B9E 80280000 mov ebx, dword ptr [esi+2880]

全过程:

功能call参数进行组包,调用明文发包call, 明文发包call内部进行加密, 写入到一个全局类对象的属性中

线程2访问该属性,进行发包

十一.偷功能

偷功能很容易就是把程序的汇编代码复制到我们的程序内进行使用

我们自己组包,自己加密,自己发送封包

好处是,彻底不走游戏的任何代码.

复制出来的汇编代码需要进行初步处理才能写到内联汇编中

1.常数全部要加上0x

2.如果有call 还要自己分析出来继续执行内容 否则还是要以来原有程序

3.mov eax,[0x12345678] 类似这种容易错误的汇编代码也要转一下

4.跳转都标注出来

例如 jnz 12345678 他是要跳转到目的代码地址执行的,我们偷出来的代码已经不是原来的地址了

所以要通过标签修改跳转的地址。

我们把加密call的代码全部偷出来


push    ebp
push    ebx
push    esi
push    edi
mov     edi, dword ptr [esp+0x14]
mov     edx, dword ptr [esp+0x18]
mov     esi, dword ptr [esp+0x1C]
mov     ebp, dword ptr [esp+0x20]
xor     eax, eax
xor     ebx, ebx
cmp     edx, 0
je      Label1 =========================================
mov     al, byte ptr [edi]
mov     bl, byte ptr [edi+4]
add     edi, 8
lea     ecx, dword ptr [esi+edx]
sub     ebp, esi
mov     dword ptr [esp+0x18], ecx
inc     al
cmp     dword ptr [edi+0x100], -1
je      Label2================================================
mov     ecx, dword ptr [edi+eax*4]
and     edx, 0xFFFFFFFC
 je      Label3===============================================
lea     edx, dword ptr [esi+edx-4]
mov     dword ptr [esp+0x1C], edx
mov     dword ptr [esp+0x20], ebp


Label4:
add     bl, cl
mov     edx, dword ptr [edi+ebx*4]
mov     dword ptr [edi+ebx*4], ecx
mov     dword ptr [edi+eax*4], edx
add     edx, ecx
inc     al
and     edx, 0x0FF
mov     ecx, dword ptr [edi+eax*4]
mov     ebp, dword ptr [edi+edx*4]
add     bl, cl
mov     edx, dword ptr [edi+ebx*4]
mov     dword ptr [edi+ebx*4], ecx
mov     dword ptr [edi+eax*4], edx
add     edx, ecx
inc     al
and     edx, 0x0FF
ror     ebp, 8
mov     ecx, dword ptr [edi+eax*4]
or      ebp, dword ptr [edi+edx*4]
add     bl, cl
mov     edx, dword ptr [edi+ebx*4]
mov     dword ptr [edi+ebx*4], ecx
mov     dword ptr [edi+eax*4], edx
add     edx, ecx
inc     al
and     edx, 0x0FF
ror     ebp, 8
mov     ecx, dword ptr [edi+eax*4]
or      ebp, dword ptr [edi+edx*4]
add     bl, cl
mov     edx, dword ptr [edi+ebx*4]
mov     dword ptr [edi+ebx*4], ecx
mov     dword ptr [edi+eax*4], edx
add     edx, ecx
inc     al
and     edx, 0x0FF
ror     ebp, 8
mov     ecx, dword ptr [esp+0x20]
or      ebp, dword ptr [edi+edx*4]
ror     ebp, 8
xor     ebp, dword ptr [esi]
cmp     esi, dword ptr [esp+0x1C]
mov     dword ptr [ecx+esi], ebp
lea     esi, dword ptr [esi+4]
mov     ecx, dword ptr [edi+eax*4]
jb      Label4============================================
cmp     esi, dword ptr [esp+0x18]
je      Label5===================================
mov     ebp, dword ptr [esp+0x20]


Label3:
add     bl, cl
mov     edx, dword ptr [edi+ebx*4]
mov     dword ptr [edi+ebx*4], ecx
mov     dword ptr [edi+eax*4], edx
add     edx, ecx
 inc     al
and     edx, 0x0FF
mov     edx, dword ptr [edi+edx*4]
xor     dl, byte ptr [esi]
lea     esi, dword ptr [esi+1]
mov     ecx, dword ptr [edi+eax*4]
cmp     esi, dword ptr [esp+0x18]
mov     byte ptr [ebp+esi-1], dl
jb      Label3==========================================
jmp     Label5========================================


Label2:

movzx   ecx, byte ptr [edi+eax]

Label6:
add     bl, cl
movzx   edx, byte ptr [edi+ebx]
mov     byte ptr [edi+ebx], cl
mov     byte ptr [edi+eax], dl
add     dl, cl
movzx   edx, byte ptr [edi+edx]
add     al, 1
xor     dl, byte ptr [esi]
 lea     esi, dword ptr [esi+1]
movzx   ecx, byte ptr [edi+eax]
cmp     esi, dword ptr [esp+0x18]
mov     byte ptr [ebp+esi-1], dl
jb      Label6=============================================
Label5:
dec     al
mov     byte ptr [edi-4], bl
mov     byte ptr [edi-8], al

Label1:

pop     edi
pop     esi
pop     ebx
pop     ebp
retn

最终代码


__declspec(naked) void 加密call(DWORD 秘钥,DWORD 加密长度,DWORD 加密地址,DWORD 加密地址2)
{


__asm
{
push    ebp
push    ebx
push    esi
push    edi
mov     edi, dword ptr [esp+0x14]
mov     edx, dword ptr [esp+0x18]
mov     esi, dword ptr [esp+0x1C]
mov     ebp, dword ptr [esp+0x20]
xor     eax, eax
xor     ebx, ebx
cmp     edx, 0
je      Label1
mov     al, byte ptr [edi]
mov     bl, byte ptr [edi+4]
add     edi, 8
lea     ecx, dword ptr [esi+edx]
sub     ebp, esi
mov     dword ptr [esp+0x18], ecx
inc     al
cmp     dword ptr [edi+0x100], -1
je      Label2
mov     ecx, dword ptr [edi+eax*4]
and     edx, 0xFFFFFFFC
 je      Label3
lea     edx, dword ptr [esi+edx-4]
mov     dword ptr [esp+0x1C], edx
mov     dword ptr [esp+0x20], ebp


Label4:
add     bl, cl
mov     edx, dword ptr [edi+ebx*4]
mov     dword ptr [edi+ebx*4], ecx
mov     dword ptr [edi+eax*4], edx
add     edx, ecx
inc     al
and     edx, 0x0FF
mov     ecx, dword ptr [edi+eax*4]
mov     ebp, dword ptr [edi+edx*4]
add     bl, cl
mov     edx, dword ptr [edi+ebx*4]
mov     dword ptr [edi+ebx*4], ecx
mov     dword ptr [edi+eax*4], edx
add     edx, ecx
inc     al
and     edx, 0x0FF
ror     ebp, 8
mov     ecx, dword ptr [edi+eax*4]
or      ebp, dword ptr [edi+edx*4]
add     bl, cl
mov     edx, dword ptr [edi+ebx*4]
mov     dword ptr [edi+ebx*4], ecx
mov     dword ptr [edi+eax*4], edx
add     edx, ecx
inc     al
and     edx, 0x0FF
ror     ebp, 8
mov     ecx, dword ptr [edi+eax*4]
or      ebp, dword ptr [edi+edx*4]
add     bl, cl
mov     edx, dword ptr [edi+ebx*4]
mov     dword ptr [edi+ebx*4], ecx
mov     dword ptr [edi+eax*4], edx
add     edx, ecx
inc     al
and     edx, 0x0FF
ror     ebp, 8
mov     ecx, dword ptr [esp+0x20]
or      ebp, dword ptr [edi+edx*4]
ror     ebp, 8
xor     ebp, dword ptr [esi]
cmp     esi, dword ptr [esp+0x1C]
mov     dword ptr [ecx+esi], ebp
lea     esi, dword ptr [esi+4]
mov     ecx, dword ptr [edi+eax*4]
jb      Label4
cmp     esi, dword ptr [esp+0x18]
je      Label5
mov     ebp, dword ptr [esp+0x20]


Label3:
add     bl, cl
mov     edx, dword ptr [edi+ebx*4]
mov     dword ptr [edi+ebx*4], ecx
mov     dword ptr [edi+eax*4], edx
add     edx, ecx
 inc     al
and     edx, 0x0FF
mov     edx, dword ptr [edi+edx*4]
xor     dl, byte ptr [esi]
lea     esi, dword ptr [esi+1]
mov     ecx, dword ptr [edi+eax*4]
cmp     esi, dword ptr [esp+0x18]
mov     byte ptr [ebp+esi-1], dl
jb      Label3
jmp     Label5


Label2:

movzx   ecx, byte ptr [edi+eax]

Label6:
add     bl, cl
movzx   edx, byte ptr [edi+ebx]
mov     byte ptr [edi+ebx], cl
mov     byte ptr [edi+eax], dl
add     dl, cl
movzx   edx, byte ptr [edi+edx]
add     al, 1
xor     dl, byte ptr [esi]
 lea     esi, dword ptr [esi+1]
movzx   ecx, byte ptr [edi+eax]
cmp     esi, dword ptr [esp+0x18]
mov     byte ptr [ebp+esi-1], dl
jb      Label6
Label5:
dec     al
mov     byte ptr [edi-4], bl
mov     byte ptr [edi-8], al

Label1:

pop     edi
pop     esi
pop     ebx
pop     ebp
retn

       }

}

十二.不走游戏代码自己发送封包

我们简单分析一个喊话封包,开始自己调用不走任何游戏代码

send头文件

#include "winsock.h"

有问题记得加上如下代码

#pragma comment(lib,"ws2_32.lib")


void HXSYDialog::OnBnClickedButton16()
{


byte a[100]  = {0x11,0x00,0x7E,0x00,0x00,0x00,0x00,0x02,0x00,0x31,0x31,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x60,0xA8,0x6C};
DWORD 包长 = 0x13;
DWORD 包地址 = (DWORD)a;
DWORD 加密地址 = 包地址 + 2;
DWORD 加密长度 = 包长 - 2;
DWORD 秘钥 = 0;
__asm
{

mov ecx,0x00f84ba4
        mov ecx,[ecx]
        mov ecx,[ecx]
        mov ecx,[ecx+0x4]
        mov ecx,[ecx+0x14]
        mov ecx,[ecx]
        lea ecx,[ecx+0x54]
mov 秘钥,ecx

}

加密call(秘钥,加密长度,加密地址,加密地址);


HWND 窗口句柄 =FindWindowA("Lapis Network Class",0);
DWORD A = GetWindowLongW(窗口句柄,-21);
DWORD S =*(DWORD*)(A+0x38);

send(S,(const char*)包地址,包长,0);


// TODO: 在此添加控件通知处理程序代码
}

我们分析几个明文包 ,然后调用测试

十三.收包

收包函数recv,recvfrom, WSARecv

(收包的参数包地址和长度 是准备接受的包地址 和最大包长)

这是必然的,因为调用前是不可能知道收到的内容和具体长度的,只有执行完毕才会获得

recv 的实际包长 在返回值eax

WSARecv 在第四个参数

十四.粘包

我们发现断的时间越长 收包越大 原因很简单 因为可能沾包

当我们线程停住的时候,服务器依然在给我们发包,这个时候就会一起接收到

那么停住的时间越长,包就粘的越长.

每个独立的封包都有很明显的划分方法,所以拆包并不难

十五.明文收包和解密call

收包我们收到的肯定也是服务器发给我们的加密封包

加密封包经过解密处理,把里面的数据写入到对应内存位置,这样我们就在界面上显示相应的反馈了.

明文收包和明文发包方向是反的

要找去哪里了,所以是下访问断追其去向

我们发现   lpbuffers  +4里的包地址  是固定的, 是否固定其实无所谓

那么我们想追解密函数 就要对  他进行访问断

当然 通过我们自己喊话等方式 让其断下  

2个拷贝的位置都可以

再对拷贝的地址下访问断

返回就是解密call

同时也是明文收包的位置了

然后我们发现    加密和解密是同一个函数

加密和解密是一个可逆的过程

正常我们其实直接搜索call 就可以找到明文收包的位置了

十六.线程发包实现的原理和方法

上面的例子学习完毕以后,实际上大家可以在追线程发包的时候还会存在一些疑惑

为什么一定要追到不变的地址,还有什么其他情况等等

其实,

线程发包本质是两条线交互信息.

那么无论是全局变量还是全局类对象下面的属性

肯定都是不变的地址

通信必然要写到这里

当然还有线程其他的通信方式

我们以后学习更多线程的知识再进行讲解

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
客户端和服务器之间的加密解密可以使用SSL(Secure Sockets Layer)协议来实现。SSL是一种加密通信协议,它使用公钥加密和私钥解密的方式来保护数据传输的安全。 Python标准库中提供了ssl模块,可以使用该模块来实现SSL加密解密。下面是一个简单的示例代码: 服务端代码: ```python import socket import ssl def main(): # 创建socket对象 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 绑定IP地址和端口号 server_address = ('localhost', 8000) server_socket.bind(server_address) # 开始监听 server_socket.listen(1) # 等待客户端连接 print('等待客户端连接...') client_socket, client_address = server_socket.accept() # 使用ssl装socket对象 ssl_socket = ssl.wrap_socket(client_socket, server_side=True, certfile='server.crt', keyfile='server.key') # 接收客户端数据 data = ssl_socket.recv(1024) print('接收到客户端数据:', data.decode()) # 发送数据给客户端 message = 'Hello, client!' ssl_socket.send(message.encode()) # 关闭连接 ssl_socket.close() server_socket.close() if __name__ == '__main__': main() ``` 客户端代码: ```python import socket import ssl def main(): # 创建socket对象 client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 连接服务器 server_address = ('localhost', 8000) client_socket.connect(server_address) # 使用ssl装socket对象 ssl_socket = ssl.wrap_socket(client_socket, ca_certs='server.crt') # 发送数据给服务器 message = 'Hello, server!' ssl_socket.send(message.encode()) # 接收服务器数据 data = ssl_socket.recv(1024) print('接收到服务器数据:', data.decode()) # 关闭连接 ssl_socket.close() if __name__ == '__main__': main() ``` 在上述代码中,服务端使用`ssl.wrap_socket()`方法将原始的socket对象装成ssl socket,同时指定了服务器证书和私钥的位置。客户端也使用`ssl.wrap_socket()`方法将原始的socket对象装成ssl socket,并指定了服务器证书的位置。这样客户端和服务器之间的通信就可以通过SSL协议进行加密解密了。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值