android清除任务列表

注意:清除任务列表必须在root权限下进行

要想实现对任务列表的清除,我们要反射函数removeTask来实现。但是只有包含系统签名的文件才能实现对任务列表的清除,所以难点就在怎么突破这种验证。

我的思路:

1.首先找到包含权限"android.permission.GET_TASKS"和"android.permission.REORDER_TASKS"的系统文件。

2.编写反射清除任务列表的代码,并生成apk。

3.ptrace找到的系统进程,并将我们的apk注入到其中。

实现框架:

通过将系统进程作为傀儡进程来突破系统签名验证,

实现过程:

首先找到包含两种权限的系统进程,可以在packages.xml中去找,我找的是com.android.systemui这个文件,其包含两种权限,并且是常驻进程,那就那它开刀了。

先编写注入代码:

#include <stdio.h>    
#include <stdlib.h>    
#include <sys/ptrace.h>
#include <sys/wait.h>    
#include <sys/mman.h>    
#include <dlfcn.h>    
#include <dirent.h>    
#include <unistd.h>    
#include <string.h>    
#include <elf.h>
#include <getopt.h>

// FIXME(ssx): add #cgo CFLAG in header
//#define GOLANG 1

#define CPSR_T_MASK     ( 1u << 5 )    

#if defined(__amd64__)
  #include <sys/reg.h>
  #include <sys/user.h>
  #include <sys/syscall.h>
  #define LOGD(fmt, args...) {printf("[D] "); printf(fmt, ##args); }
  #define LOGI(fmt, args...) {printf("[I] "); printf(fmt, ##args); }
  #define LOGE(fmt, args...) {printf("[E] "); printf(fmt, ##args); exit(1);}
  const char *libc_path = "/lib/x86_64-linux-gnu/libc-2.13.so";    
  const char *linker_path = "/lib/x86_64-linux-gnu/ld-2.13.so";    
#elif defined(__i386__)
  #define pt_regs         user_regs_struct    
  #define LOGD(fmt, args...) {printf("[D] "); printf(fmt, ##args); printf("\n");}
  #define LOGI(fmt, args...) {printf("[I] "); printf(fmt, ##args); printf("\n");}
  #define LOGE(fmt, args...) {printf("[E] "); printf(fmt, ##args); printf("\n"); exit(1);}
  const char *libc_path = "/system/lib/libc.so";    
  const char *linker_path = "/system/bin/linker";    
#elif defined(__arm__)
  #include <android/log.h>    
  #include <asm/user.h>    
  #include <asm/ptrace.h>    
  #define  LOG_TAG "INJECT"    
  #define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args)
  #define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, fmt, ##args)
  #define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, fmt, ##args)
  const char *libc_path = "/system/lib/libc.so";    
  const char *linker_path = "/system/bin/linker";    
#else
  #error "Unsupported platform"
#endif
    
#define DEBUG_PRINT(format,args...)  LOGD(format, ##args)    
typedef unsigned long ulong;
    
int ptrace_readdata(pid_t pid,  const uint8_t *src, uint8_t *buf, size_t size){    
    uint32_t i, j, remain;    
    uint8_t *laddr;    
    
    union u {    
        long val;    
        char chars[sizeof(long)];    
    } d;
    
    j = size / sizeof(ulong);    
    remain = size % sizeof(ulong);    
    
    laddr = buf;    
    
    for (i = 0; i < j; i ++) {    
        d.val = ptrace(PTRACE_PEEKTEXT, pid, src, 0);    
        memcpy(laddr, d.chars, sizeof(ulong));    
        src += sizeof(ulong);    
        laddr += sizeof(ulong);    
    }    
    
    if (remain > 0) {    
        d.val = ptrace(PTRACE_PEEKTEXT, pid, src, 0);    
        memcpy(laddr, d.chars, remain);    
    }    
    
    return 0;    
}    
    
int ptrace_writedata(pid_t pid, uint8_t *dest, const uint8_t *data, size_t size)    
{    
    uint32_t i, j, remain;    
    const uint8_t *laddr;    
    
    union u {    
        long val;    
        char chars[sizeof(long)];    
    } d;    
    
    j = size / sizeof(ulong);    
    remain = size % sizeof(ulong);    
    
    laddr = data;
    
    for (i = 0; i < j; i ++) {    
        memcpy(d.chars, laddr, sizeof(ulong));    
        ptrace(PTRACE_POKETEXT, pid, dest, d.val);    
    
        dest  += sizeof(ulong);    
        laddr += sizeof(ulong);    
    }    
    
    if (remain > 0) {    
        d.val = ptrace(PTRACE_PEEKTEXT, pid, dest, 0);    
        for (i = 0; i < remain; i ++) {    
            d.chars[i] = *laddr ++;    
        }    
    
        ptrace(PTRACE_POKETEXT, pid, dest, d.val);    
    }    
    
    return 0;    
}    
    
#if defined(__arm__)    
int ptrace_call(pid_t pid, ulong addr, long *params, uint32_t num_params, struct pt_regs* regs)    
{    
    uint32_t i;
    for (i = 0; i < num_params && i < 4; i ++) {
        regs->uregs[i] = params[i];
    }    
    
    //    
    // push remained params onto stack    
    //    
    if (i < num_params) {
        regs->ARM_sp -= (num_params - i) * sizeof(long);
        ptrace_writedata(pid, (void *)regs->ARM_sp, (uint8_t *)¶ms[i], (num_params - i) * sizeof(long));
    } 
    
    regs->ARM_pc = addr; 
    if (regs->ARM_pc & 1) { 
        /* thumb */ 
        regs->ARM_pc &= (~1u); 
        regs->ARM_cpsr |= CPSR_T_MASK;
    } else { 
        /* arm */ 
        regs->ARM_cpsr &= ~CPSR_T_MASK;
    } 
    
    regs->ARM_lr = 0; 
    
    if (ptrace_setregs(pid, regs) == -1 
            || ptrace_continue(pid) == -1) {
        printf("error\n");
        return -1;
    } 
    
    int stat = 0;
    waitpid(pid, &stat, WUNTRACED);
    while (stat != 0xb7f) {
        if (ptrace_continue(pid) == -1) {
            printf("error\n");
            return -1;
        }
        waitpid(pid, &stat, WUNTRACED);
    }
    return 0;    
}
    
#elif defined(__i386__)    
long ptrace_call(pid_t pid, ulong addr, long *params, uint32_t num_params, struct pt_regs * regs)    
{    
    regs->esp -= (num_params) * sizeof(long) ;    
    ptrace_writedata(pid, (void *)regs->esp, (uint8_t *)params, (num_params) * sizeof(long));    
    
    long tmp_addr = 0x00;    
    regs->esp -= sizeof(long);    
    ptrace_writedata(pid, regs->esp, (char *)&tmp_addr, sizeof(tmp_addr));     
    
    regs->eip = addr;
    
    if (ptrace_setregs(pid, regs) == -1     
            || ptrace_continue(pid) == -1) {    
        printf("error\n");    
        return -1;    
    }    
    
    int stat = 0;  
    waitpid(pid, &stat, WUNTRACED);  
    while (stat != 0xb7f) {   // ?
        if (ptrace_continue(pid) == -1) {  
            printf("error\n");  
            return -1;  
        }  
        waitpid(pid, &stat, WUNTRACED);  
    }  
    
    return 0;    
}    
#elif defined(__amd64__)    
long ptrace_call(pid_t pid, ulong addr, long *params, uint32_t num_params, struct pt_regs * regs)    
{    
    regs->rsp -= num_params * sizeof(long); 
    ptrace_writedata(pid, (void*)regs->rsp, (uint8_t *)params, num_params * sizeof(long));    
    
    ulong tmp_addr = 0x00;    
    regs->rsp -= sizeof(ulong);    
    ptrace_writedata(pid, (void*)regs->rsp, (char*)&tmp_addr, sizeof(tmp_addr));
    
    regs->rip = addr;
    
    if (ptrace_setregs(pid, regs) == -1     
            || ptrace_continue( pid) == -1) {    
        printf("error\n");    
        return -1;    
    }    
    
    int stat = 0;  
    waitpid(pid, &stat, WUNTRACED);  
    while (stat != 0xb7f) {  
        if (ptrace_continue(pid) == -1) {  
            printf("error\n");  
            return -1;  
        }  
        waitpid(pid, &stat, WUNTRACED);  
    }  
    
    return 0;    
}    
#else     
#error "Not supported"    
#endif    
    
int ptrace_getregs(pid_t pid, struct pt_regs * regs){    
    if (ptrace(PTRACE_GETREGS, pid, NULL, regs) < 0) {    
        perror("ptrace_getregs: Can not get register values");    
        return -1;    
    }    
    return 0;    
}    
    
int ptrace_setregs(pid_t pid, struct pt_regs * regs){    
    if (ptrace(PTRACE_SETREGS, pid, NULL, regs) < 0) {    
        perror("ptrace_setregs: Can not set register values");    
        return -1;    
    }    
    return 0;    
}    
    
int ptrace_continue(pid_t pid){    
    if (ptrace(PTRACE_CONT, pid, NULL, 0) < 0) {    
        perror("ptrace_cont");    
        return -1;    
    }    
    return 0;    
}    
    
int ptrace_attach(pid_t pid){    
    if (ptrace(PTRACE_ATTACH, pid, NULL, 0) < 0) {    
        perror("ptrace_attach");    
        return -1;    
    }    
    
    int status = 0;    
    waitpid(pid, &status , WUNTRACED);    
    
    return 0;    
}    
    
int ptrace_detach(pid_t pid){    
    if (ptrace(PTRACE_DETACH, pid, NULL, 0) < 0) {    
        perror("ptrace_detach");    
        return -1;    
    }    
    
    return 0;    
}    
    
ulong get_module_base(pid_t pid, const char* module_name){    
    FILE *fp;    
    ulong addr = 0;    
    char *pch;    
    char filename[32];    
    char line[1024];    
    
    if (pid < 0) {    
        /* self process */    
        snprintf(filename, sizeof(filename), "/proc/self/maps", pid);    
    } else {    
        snprintf(filename, sizeof(filename), "/proc/%d/maps", pid);    
    }    
    
    fp = fopen(filename, "rb");    
    
    if (fp != NULL) {    
        while (fgets(line, sizeof(line), fp)) {    
            if (strstr(line, module_name)) {    
                pch = strtok(line, "-" );    
                addr = strtoul(pch, NULL, 16);
    
                if (addr == 0x8000)    
                    addr = 0;    
                break;    
            }    
        }    
    
        fclose(fp) ;    
    }    
    
    return addr;    
}
    
void* get_remote_addr(pid_t target_pid, const char* module_name, ulong local_addr){    
    ulong local_handle, remote_handle;
    
    local_handle = get_module_base(-1, module_name); 
    remote_handle = get_module_base(target_pid, module_name);    
    
    printf("[+] [%s] get_remote_addr: local[%p], remote[%p]\n", 
			module_name, local_handle, remote_handle);    
    ulong ret_addr = local_addr + remote_handle - local_handle;
    
#if defined(__i386__)    
    if (!strcmp(module_name, libc_path)) {    
        ret_addr += 2;    
    }    
#endif    
    return (void*)ret_addr;    
}    
    
int find_pid_of(const char *process_name){    
    int id;    
    pid_t pid = -1;    
    DIR* dir;    
    FILE *fp;    
    char filename[32];    
    char cmdline[256];    
    
    struct dirent * entry;    
    
    if (process_name == NULL)    
        return -1;    
    
    dir = opendir("/proc");    
    if (dir == NULL)    
        return -1;    
    
    while((entry = readdir(dir)) != NULL) {    
        id = atoi(entry->d_name);    
        if (id != 0) {    
            sprintf(filename, "/proc/%d/cmdline", id);    
            fp = fopen(filename, "r");    
            if (fp) {    
                fgets(cmdline, sizeof(cmdline), fp);    
                fclose(fp);    
    
                if (strcmp(process_name, cmdline) == 0) {    
                    /* process found */    
                    pid = id;    
                    break;    
                }    
            }    
        }    
    }    
    
    closedir(dir);    
    return pid;    
}    
    
long ptrace_retval(struct pt_regs * regs)    
{    
#if defined(__arm__)    
    return regs->ARM_r0;    
#elif defined(__i386__)    
    return regs->eax;    
#elif defined(__amd64__)    
    return regs->rax;    
#else    
#error "Not supported"    
#endif    
}    
    
long ptrace_ip(struct pt_regs * regs)    
{    
#if defined(__arm__)    
    return regs->ARM_pc;    
#elif defined(__i386__)    
    return regs->eip;    
#elif defined(__amd64__)
	return regs->rip;
#else 
#error "Not supported"    
#endif    
}    
    
int ptrace_call_wrapper(pid_t target_pid, const char * func_name, void * func_addr, long * parameters, int param_num, struct pt_regs * regs)     
{    
    printf("[+] Calling %s in target process.\n", func_name);    
    if (ptrace_call(target_pid, (ulong)func_addr, parameters, param_num, regs) == -1)    
        return -1;    
    printf("[+] Over call Start getregs.\n");
    if (ptrace_getregs(target_pid, regs) == -1)    
        return -1;    
    printf("[+] Target process returned from %s, return value=%x, pc=%x \n",     
            func_name, ptrace_retval(regs), ptrace_ip(regs));    
    return 0;    
}    
    
int inject_remote_process(pid_t target_pid, const char *library_path, 
		char *function_name, char *param, size_t param_size)    
{    
    int ret = -1;    
    void *mmap_addr, *dlopen_addr, *dlsym_addr, *dlclose_addr, *dlerror_addr;    
    void *local_handle, *remote_handle, *dlhandle;    
    uint8_t *map_base = 0;    
    uint8_t *dlopen_param1_ptr, *dlsym_param2_ptr, *saved_r0_pc_ptr, *inject_param_ptr, *remote_code_ptr, *local_code_ptr;    
    
    struct pt_regs regs, original_regs;
    extern ulong  _dlopen_addr_s, _dlopen_param1_s, _dlopen_param2_s, _dlsym_addr_s, \
        _dlsym_param2_s, _dlclose_addr_s, _inject_start_s, _inject_end_s, _inject_function_param_s, \
        _saved_cpsr_s, _saved_r0_pc_s;    
    
    uint32_t code_length;    
    long parameters[10];    
    
    printf("[+] Injecting process: %d\n", target_pid);    
    
    if (ptrace_attach(target_pid) == -1)    
        goto exit;    
    
    if (ptrace_getregs(target_pid, &regs) == -1)    
        goto exit;    
    
    /* save original registers */    
    memcpy(&original_regs, &regs, sizeof(regs));    
    
    mmap_addr = get_remote_addr(target_pid, libc_path, (ulong)mmap);    
    printf("[+] Remote mmap address: %x\n", mmap_addr);    
    
    /* call mmap */    
    parameters[0] = 0;  // addr    
    parameters[1] = 0x4000; // size    
    parameters[2] = PROT_READ | PROT_WRITE | PROT_EXEC;  // prot    
    parameters[3] =  MAP_ANONYMOUS | MAP_PRIVATE; // flags    
    parameters[4] = 0; //fd    
    parameters[5] = 0; //offset    
    
    if (ptrace_call_wrapper(target_pid, "mmap", mmap_addr, parameters, 6, &regs) == -1)    
        goto exit;    
    
    map_base = (uint8_t*)ptrace_retval(&regs);
    
    dlopen_addr  = get_remote_addr(target_pid, linker_path, (ulong)dlopen );    
    dlsym_addr   = get_remote_addr(target_pid, linker_path, (ulong)dlsym );    
    dlclose_addr = get_remote_addr(target_pid, linker_path, (ulong)dlclose );    
    dlerror_addr = get_remote_addr(target_pid, linker_path, (ulong)dlerror );    
    
    printf("[+] Get imports: \n\tdlopen: %p, \n\tdlsym: %p, \n\tdlclose: %p, \n\tdlerror: %p\n",    
            dlopen_addr, dlsym_addr, dlclose_addr, dlerror_addr);    
    
    printf("library path = %s\n", library_path);    
    ptrace_writedata(target_pid, map_base, library_path, strlen(library_path) + 1);    
    
    parameters[0] = (long)map_base;       
    parameters[1] = RTLD_NOW| RTLD_GLOBAL;     
    
    if (ptrace_call_wrapper(target_pid, "dlopen", dlopen_addr, parameters, 2, &regs) == -1)    
        goto exit;    
    
    void* sohandle = (void*)ptrace_retval(&regs);    
    
#define FUNCTION_NAME_ADDR_OFFSET       0x100    
    ptrace_writedata(target_pid, map_base + FUNCTION_NAME_ADDR_OFFSET, function_name, strlen(function_name) + 1);    
    parameters[0] = (long)sohandle;       
    parameters[1] = (long)(map_base + FUNCTION_NAME_ADDR_OFFSET);
    
    if (ptrace_call_wrapper(target_pid, "dlsym", dlsym_addr, parameters, 2, &regs) == -1)    
        goto exit;    
    
    void* hook_entry_addr = (void*)ptrace_retval(&regs);
    printf("hook_entry_addr = %p\n", hook_entry_addr);    
    
#define FUNCTION_PARAM_ADDR_OFFSET      0x200    
    ptrace_writedata(target_pid, map_base + FUNCTION_PARAM_ADDR_OFFSET, param, strlen(param) + 1);
    parameters[0] = (long)(map_base + FUNCTION_PARAM_ADDR_OFFSET);
  
    if (ptrace_call_wrapper(target_pid, "hook_entry", hook_entry_addr, parameters, 1, &regs) == -1)    
        goto exit;        
    
    //printf("Press enter to dlclose and detach\n");
    //getchar();
    parameters[0] = (long)sohandle;       
    
    if (ptrace_call_wrapper(target_pid, "dlclose", dlclose, parameters, 1, &regs) == -1)    
        goto exit;    
    
    /* restore */    
	printf("detach\n");
    ptrace_setregs(target_pid, &original_regs);    
    ptrace_detach(target_pid);    
    ret = 0;    
    
exit:    
    return ret;    
}    


int main( int argc, char** argv )
{
    int target_pid;
    char *param = "param";
	char* sopath="/data/local/tmp/libpackage.so";
	target_pid = find_pid_of("com.android.systemui");
	
    inject_remote_process(target_pid, sopath, "hook_entry", param, strlen(param));

    return 0;
}
这个用的是网上的代码,没什么可讲的,不理解的可以去网上搜搜,讲解很详细。

下面编写实现加载指定apk的代码:

#include <unistd.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <android/log.h> 
#include <elf.h> 
#include <fcntl.h> 
#include <jni.h>
#include <dlfcn.h>

int invoke_dex_method(const char* dexPath, const char* dexOptDir, const char* className, const char* methodName, int argc, char *argv[]);

int hook_entry(char * a){ 
    printf("Hook success, pid = %d\n", getpid()); 
    printf("Hello %s\n", a); 
    int ret = invoke_dex_method("/data/app-release.apk","/data/data/com.android.systemui/cache","com/tencent/qlauncher/home/classtaskjar/HookCallback","dexInject",0,NULL);
    printf("Hello %d\n",ret);
    return 0; 
}

JNIEnv* (*getJNIEnv)();
/**
* PARAM:
* dexPath要注入的apk/jar路径
* dexOptDir  缓存路径,注意需要目标应用进程中可写的目录
* className  执行方法所在类名
* methodName 执行的方法名
* argc   参数之流这里没有使用
* argv  参数之流这里没有使用
*/
int invoke_dex_method(const char* dexPath, const char* dexOptDir, const char* className, const char* methodName, int argc, char *argv[]) {
	printf("Invoke dex Start");
	//获取JNIEnv
    void* handle = dlopen("/system/lib/libandroid_runtime.so", RTLD_NOW);
    getJNIEnv = dlsym(handle, "_ZN7android14AndroidRuntime9getJNIEnvEv");
    JNIEnv* env = getJNIEnv();

    //调用ClassLoader中的getSystemClassLoader方法获取当前进程的ClassLoader
    jclass classloaderClass = (*env)->FindClass(env,"java/lang/ClassLoader");
    jmethodID getsysloaderMethod = (*env)->GetStaticMethodID(env,classloaderClass, "getSystemClassLoader", "()Ljava/lang/ClassLoader;");
    jobject loader = (*env)->CallStaticObjectMethod(env, classloaderClass, getsysloaderMethod);

    //以进程现有的ClassLoader、要注入的dex路径为参数构造注入后的DexClassLoader
    jstring dexpath = (*env)->NewStringUTF(env, dexPath);
    jstring dex_odex_path = (*env)->NewStringUTF(env,dexOptDir);
    jclass dexLoaderClass = (*env)->FindClass(env,"dalvik/system/DexClassLoader");
    jmethodID initDexLoaderMethod = (*env)->GetMethodID(env, dexLoaderClass, "<init>", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)V");
    jobject dexLoader = (*env)->NewObject(env, dexLoaderClass, initDexLoaderMethod,dexpath,dex_odex_path,NULL,loader);

    //获取新出炉的DexClassLoader中findClass方法加载dex中要执行代码所在类
    jmethodID findclassMethod = (*env)->GetMethodID(env,dexLoaderClass,"findClass","(Ljava/lang/String;)Ljava/lang/Class;");
    jstring javaClassName = (*env)->NewStringUTF(env,className);
    jclass javaClientClass = (*env)->CallObjectMethod(env,dexLoader,findclassMethod,javaClassName);
	if (!javaClientClass) {
        printf("Failed to load target class %s", className);
        return -1;
    }

    //获取注入dex中要执行的方法
    jmethodID start_inject_method = (*env)->GetStaticMethodID(env, javaClientClass, methodName, "()V");
	if (!start_inject_method) {
        printf("Failed to load target method %s", methodName);
        return -1;
    }
    //执行之注意目标方法必须是静态公有的
    (*env)->CallStaticVoidMethod(env,javaClientClass,start_inject_method);
	printf("Invoke dex OK");
}
这里有一点需要注意:我们在/data/data/com.android.systemui文件夹下是不存在cache这个文件夹的,我们可以新建一个文件夹,需要可写属性,并且要将其用户组改为com.android.systemui相同的,如图:



有了这些操作,下一步就是要编写我们自己的反射代码了,创建一个空项目,如下:

public class HookCallback {

    public static void dexInject(){
        Log.d("clear_task", "this is dex code,welcome to HookTool~");
        ActivityManager mActivityManager = null;
        Method mRemoveTask;

        try {
            Class<?> ActivityThread = Class.forName("android.app.ActivityThread");

//            Method[] methods = ActivityThread.getMethods();
//            for (Method method : methods) {
//                Log.e("clear_task", method.getName());
//            }

            Method method = ActivityThread.getMethod("currentActivityThread");
            Object currentActivityThread = method.invoke(ActivityThread);//获取currentActivityThread 对象

            Method method2 = currentActivityThread.getClass().getMethod("getApplication");
            Context CONTEXT_INSTANCE =(Context)method2.invoke(currentActivityThread);//获取 Context对象

            Log.e("clear_task","CONTEXT_INSTANCE:"+CONTEXT_INSTANCE);

            Class<?> activityManagerClass = Class.forName("android.app.ActivityManager");
            mActivityManager = (ActivityManager) CONTEXT_INSTANCE.getSystemService(Context.ACTIVITY_SERVICE);

            mRemoveTask = activityManagerClass.getMethod("removeTask", new Class[] { int.class, int.class });
            mRemoveTask.setAccessible(true);

            Log.e("clear_task","dexInject start");
            List<ActivityManager.RecentTaskInfo> recents = mActivityManager.getRecentTasks(Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE);
            // Start from 1, since we don't want to kill ourselves!
            for( int i=1; i < recents.size(); i++ ) {
                Log.e("clear_task","invoke start --------");
                mRemoveTask.invoke(mActivityManager, recents.get(i).persistentId, 0 );
                Log.e("clear_task","dexInject --------");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
然后将它们拷贝到虚拟机中,运行看效果:


运行我们的代码:


然后查看手机看到:


成功实现了对任务列表的清空。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值