[LINUX C]利用ptrace实现文件读写等linux系统调用的hook(代码)

利用ptrace实现文件读写等系统调用的hook:

#include <stdio.h> 
#include <stdlib.h> 
#include <sys/ptrace.h> 
#include <sys/wait.h> 
#include <sys/types.h> 
#include <sys/reg.h> 
#include <sys/user.h> 
#include <unistd.h> 
#include <errno.h> 
#include <sys/syscall.h> 
#include <sys/mman.h> 
#include <memory.h> 
#include <sys/uio.h> 
#include "util.h" 
//#if defined(__x86_64__) 
//#define REGX ORIG_RAX * 8 
//#else 
//#define REGX ORIG_EAX * 4 
//#endif 
#define RECORD_BEFORE_EXE(a)  static bool arrRunRecord[4194500] = { 0 }; \ 
                                if (bClean) { \
                                    arrRunRecord[a] = false; \ 
                                    return; \ 
                                } \ 
                                if (arrRunRecord[a]) { \ 
                                    arrRunRecord[a] = false; \ 
                                    return; \ 
                                } \ 
                                arrRunRecord[a] = true; 
#define RECORD_AFTER_EXE(a)   static bool arrRunRecord[4194500] = { 0 }; \ 
                                if (bClean) { \ 
                                    arrRunRecord[a] = false; \ 
                                    return; \ 
                                } \ 
                                if (!arrRunRecord[a]) { \ 
                                    arrRunRecord[a] = true; \ 
                                    return; \ 
                                } \ 
                                arrRunRecord[a] = false; 
                                
struct i386_user_regs_struct { 
    unsigned int                ebx; 
    unsigned int                ecx; 
    unsigned int                edx; 
    unsigned int                esi; 
    unsigned int                edi; 
    unsigned int                ebp; 
    unsigned int                eax; 
    unsigned int                xds; 
    unsigned int                xes; 
    unsigned int                xfs; 
    unsigned int                xgs; 
    unsigned int                orig_eax; 
    unsigned int                eip; 
    unsigned int                xcs; 
    unsigned int                eflags; 
    unsigned int                esp; 
    unsigned int                xss; 
}; 

typedef union _UN_REGS { 
    struct user_regs_struct         x86_64_r; 
    struct i386_user_regs_struct    i386_r; 
} UN_REGS;

typedef struct _PROCESS_INFO { 
    bool            bLinux32; 
    int             nSysCallId; 
    unsigned long   nParam1; 
    unsigned long   nParam2; 
    unsigned long   nParam3; 
    unsigned long   nParam4; 
    unsigned long   nParam5; 
    unsigned long   nParam6; 
} ST_PROCESS_INFO; 

typedef void(*pSysCallProcFun)(ST_PROCESS_INFO *pParam, pid_t pid, bool bClean); 

static pSysCallProcFun g_afuncProcessor64[4000] = { 0 }; 
static pSysCallProcFun g_afuncProcessor32[4000] = { 0 }; 
bool g_bArrProcessInfo[4194500]     = { 0 }; 
bool g_bArrProcessLogged[4194500]   = { 0 }; 
long g_nPrograms                    = 0; 

static inline void get_syscall_args(pid_t pid, ST_PROCESS_INFO *pData) 
{ 
    struct iovec iov; 
    UN_REGS regs; 
    iov.iov_base = &regs; 
    iov.iov_len = sizeof(regs); 
    ptrace(PTRACE_GETREGSET, pid, 1, &iov); 
    if (iov.iov_len == sizeof(i386_user_regs_struct)) { 
        // 32-bit 
        pData->bLinux32 = true; 
        pData->nSysCallId   = (unsigned long)((UN_REGS *)iov.iov_base)->i386_r.orig_eax; 
        pData->nParam1      = (unsigned long)((UN_REGS *)iov.iov_base)->i386_r.ebx; 
        pData->nParam2      = (unsigned long)((UN_REGS *)iov.iov_base)->i386_r.ecx; 
        pData->nParam3      = (unsigned long)((UN_REGS *)iov.iov_base)->i386_r.edx; 
        pData->nParam4      = (unsigned long)((UN_REGS *)iov.iov_base)->i386_r.esi; 
        pData->nParam5      = (unsigned long)((UN_REGS *)iov.iov_base)->i386_r.edi; 
        pData->nParam6      = (unsigned long)((UN_REGS *)iov.iov_base)->i386_r.ebp; 
    } 
    else { 
        // 64-bit 
        pData->bLinux32 = false; 
        pData->nSysCallId = ((UN_REGS *)iov.iov_base)->x86_64_r.orig_rax; 
        pData->nParam1 = (unsigned long)((UN_REGS *)iov.iov_base)->x86_64_r.rdi; 
        pData->nParam2 = (unsigned long)((UN_REGS *)iov.iov_base)->x86_64_r.rsi; 
        pData->nParam3 = (unsigned long)((UN_REGS *)iov.iov_base)->x86_64_r.rdx; 
        pData->nParam4 = (unsigned long)((UN_REGS *)iov.iov_base)->x86_64_r.r10; 
        pData->nParam5 = (unsigned long)((UN_REGS *)iov.iov_base)->x86_64_r.r8; 
        pData->nParam6 = (unsigned long)((UN_REGS *)iov.iov_base)->x86_64_r.r9; 
    } 
} 

static inline void ptrace_continue(pid_t pid, int signum) 
{ 
    ptrace(PTRACE_SYSCALL, pid, 0, signum); 
}

static inline void ptrace_trace_child(pid_t pid) 
{ 
    ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACEEXIT | //PTRACE_O_TRACEEXEC | PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACECLONE | PTRACE_O_TRACEVFORK | PTRACE_O_TRACEFORK); 
} 

static inline void ptrace_detach(pid_t pid, int signum) 
{ 
    ptrace(PTRACE_DETACH, pid, 0, signum); 
} 

static inline long ptrace_get_child(pid_t pid) 
{ 
    long child = 0; 
    ptrace(PTRACE_GETEVENTMSG, pid, 0, &child); return child; 
} 

static inline void ptrace_get_regs(pid_t pid, struct user_regs_struct *regs) 
{ 
    ptrace(PTRACE_GETREGS, pid, NULL, regs); 
} 

static inline void record_read_syscall(pid_t pid, int fd) 
{ 
    char abs_file[MY_PATH_MAX] = { '\0' }; 
    ssize_t parese_ret = parse_fd_to_filename(abs_file, sizeof(abs_file), fd, pid); 
    if (parese_ret != -1) { 
        if (!is_directory(abs_file)) { 
            print_dep_file("read", abs_file, "Read", pid); 
        } 
    } 
} 

static inline void record_write_syscall(pid_t pid, int fd) 
{ 
    char abs_file[MY_PATH_MAX] = { '\0' }; 
    ssize_t parese_ret = parse_fd_to_filename(abs_file, sizeof(abs_file), fd, pid); 
    if (parese_ret != -1) { 
        if (!is_directory(abs_file)) { 
            print_dep_file("write", abs_file, "Write", pid); 
        } 
    } 
} 

static inline void record_read_link(pid_t pid, const char *filename) 
{ 
    char abs_file[MY_PATH_MAX]; 
    parse_to_abs_filepath(abs_file, filename, pid); 
    if (!is_directory(abs_file)) { 
        print_dep_file("read", abs_file, "Read", pid); 
    } 
} 

static inline void record_read_linkat(pid_t pid, int dirfd, const char *filename) 
{ 
    char abs_file[MY_PATH_MAX]; 
    parse_to_abs_filepath_with_dirfd(abs_file, dirfd, filename, pid); 
    if (!is_directory(abs_file)) { 
        print_dep_file("read", abs_file, "Read", pid); 
    } 
} 

static inline void record_mmap_syscall(pid_t pid, int prob, int fd) { 
    if (prob & PROT_READ) { 
        char abs_file[MY_PATH_MAX] = { '\0' }; 
        ssize_t parese_ret = parse_fd_to_filename(abs_file, sizeof(abs_file), fd, pid); 
        if (parese_ret != -1) { 
            if (!is_directory(abs_file)) { 
                print_dep_file("read", abs_file, "Read", pid); 
            } 
        } 
    } 

    //if (prob & PROT_WRITE) { 
    //    char abs_file[MY_PATH_MAX] = { '\0' }; 
    //    ssize_t parese_ret = parse_fd_to_filename(abs_file, sizeof(abs_file), fd, pid); 
    //    if (parese_ret != -1) { 
    //        if (!is_directory(abs_file)) { 
    //            print_dep_file("write", abs_file, "Write", pid); 
    //        } 
    //    } 
    //} 
} 

static inline void record_rename_syscall(pid_t pid, const char *old, const char *new_name) 
{ 
    char abs_old_file[MY_PATH_MAX]; 
    parse_to_abs_filepath(abs_old_file, old, pid); 
    char abs_new_file[MY_PATH_MAX]; 
    parse_to_abs_filepath(abs_new_file, new_name, pid); 
    if (!is_directory(abs_new_file)) { 
        print_rename_file("rename", abs_old_file, abs_new_file, "Rename", pid); 
        return; 
    } 

    print_rename_files_in_dir(abs_new_file, abs_old_file, abs_new_file, pid); 
} 

static inline void record_renameat_syscall(pid_t pid, int olddirfd, const char *old_name, int newdirfd, const char *new_name) 
{ 
    char abs_old_file[MY_PATH_MAX]; 
    parse_to_abs_filepath_with_dirfd(abs_old_file, olddirfd, old_name, pid); 
    char abs_new_file[MY_PATH_MAX]; 
    parse_to_abs_filepath_with_dirfd(abs_new_file, newdirfd, new_name, pid); 
    if (!is_directory(abs_new_file)) { 
        print_rename_file("renameat", abs_old_file, abs_new_file, "Rename", pid); 
        return; 
    } 

    print_rename_files_in_dir(abs_new_file, abs_old_file, abs_new_file, pid); 
} 

static inline void record_rmdir_syscall(pid_t pid, const char *file_path) { 
    char abs_file[MY_PATH_MAX] = { 0 }; 
    parse_to_abs_filepath(abs_file, 

        //print_delete_files_in_dir_by_regex("rmdir", abs_file, pid); return; 
    } 

    print_delete_files_in_dir("rmdir", abs_file, abs_file, pid); 
} 

static inline void record_unlink_syscall(pid_t pid, const char *file_path) 
{ 
    char abs_file[MY_PATH_MAX] = { 0 }; 
    parse_to_abs_filepath(abs_file, file_path, pid); 
    if (!is_directory(abs_file)) { 
        print_dep_file("unlink", abs_file, "Delete", pid); 
        //print_delete_files_in_dir_by_regex("unlink", abs_file, pid); return; 
    } 

    print_delete_files_in_dir("unlink", abs_file, abs_file, pid); 
} 

static inline void record_unlinkat_syscall(pid_t pid, int dirfd, const char *file_path) 
{ 
    char abs_file[MY_PATH_MAX] = { 0 }; 
    parse_to_abs_filepath_with_dirfd(abs_file, dirfd, file_path, pid); 
    if (!is_directory(abs_file)) { 
        print_dep_file("unlinkat", abs_file, "Delete", pid); 
        //print_delete_files_in_dir_by_regex("unlinkat", abs_file, pid); 
        return; 
    } 

    print_delete_files_in_dir("unlinkat", abs_file, abs_file, pid); 
} 

static inline void record_process_info(pid_t pid) { 
    print_dep_file("process", "", "Process", pid); 
} 

void sys_execve_processor(ST_PROCESS_INFO *pParam, pid_t pid, bool bClean) 
{ 
    RECORD_AFTER_EXE(pid); 
    activate(pid); 
    record_process_info(pid); 
    g_bArrProcessLogged[pid] = true; 
} 

void sys_write_processor(ST_PROCESS_INFO *pParam, pid_t pid, bool bClean) 
{ 
    RECORD_BEFORE_EXE(pid); 
    record_write_syscall(pid, (int)pParam->nParam1);
} 

void sys_pwrite64_processor(ST_PROCESS_INFO *pParam, pid_t pid, bool bClean) 
{ 
    RECORD_BEFORE_EXE(pid); 
    record_write_syscall(pid, (int)pParam->nParam1); 
} 

void sys_writev_processor(ST_PROCESS_INFO *pParam, pid_t pid, bool bClean) 
{ 
    RECORD_BEFORE_EXE(pid); 
    record_write_syscall(pid, (int)pParam->nParam1); 
} 

void sys_pwritev_processor(ST_PROCESS_INFO *pParam, pid_t pid, bool bClean) 
{ 
    RECORD_BEFORE_EXE(pid); 
    record_write_syscall(pid, (int)pParam->nParam1); 
} 

void sys_read_processor(ST_PROCESS_INFO *pParam, pid_t pid, bool bClean) 
{ 
    RECORD_BEFORE_EXE(pid); 
    record_read_syscall(pid, (int)pParam->nParam1); 
} 

void sys_readv_processor(ST_PROCESS_INFO *pParam, pid_t pid, bool bClean) 
{ 
    RECORD_BEFORE_EXE(pid); 
    record_read_syscall(pid, (int)pParam->nParam1); 
} 

void sys_preadv_processor(ST_PROCESS_INFO *pParam, pid_t pid, bool bClean) 
{ 
    RECORD_BEFORE_EXE(pid); 
    record_read_syscall(pid, (int)pParam->nParam1); 
} 

void sys_pread64_processor(ST_PROCESS_INFO *pParam, pid_t pid, bool bClean) 
{ 
    RECORD_BEFORE_EXE(pid); 
    record_read_syscall(pid, (int)pParam->nParam1); 
} 

void sys_readlink_processor(ST_PROCESS_INFO *pParam, pid_t pid, bool bClean) 
{ 
    RECORD_BEFORE_EXE(pid); 
    char file_name[MY_PATH_MAX] = { '\0' }; 
    read_string(pid, pParam->nParam1, file_name); 
    record_read_link(pid, file_name); 
} 

void sys_readlinkat_processor(ST_PROCESS_INFO *pParam, pid_t pid, bool bClean) 
{ 
    RECORD_BEFORE_EXE(pid); 
    char file_name[MY_PATH_MAX] = { '\0' }; 
    read_string(pid, pParam->nParam2, file_name); 
    record_read_linkat(pid, (int)pParam->nParam1, file_name); 
} 

void sys_mmap_processor(ST_PROCESS_INFO *pParam, pid_t pid, bool bClean) 
{ 
    RECORD_BEFORE_EXE(pid); 
    record_mmap_syscall(pid, pParam->nParam3, pParam->nParam5); 
} 

void sys_rename_processor(ST_PROCESS_INFO *pParam, pid_t pid, bool bClean) 
{ 
    RECORD_AFTER_EXE(pid); 
    char old_file[MY_PATH_MAX] = { '\0' }; 
    read_string(pid, pParam->nParam1, old_file); 
    char new_file[MY_PATH_MAX] = { '\0' }; 
    read_string(pid, pParam->nParam2, new_file); 
    record_rename_syscall(pid, old_file, new_file); 
} 

void sys_renameat_processor(ST_PROCESS_INFO *pParam, pid_t pid, bool bClean) 
{ 
    RECORD_AFTER_EXE(pid); 
    char old_file[MY_PATH_MAX] = { '\0' }; 
    read_string(pid, pParam->nParam2, old_file); 
    char new_file[MY_PATH_MAX] = { '\0' }; 
    read_string(pid, pParam->nParam4, new_file); 
    record_renameat_syscall(pid, pParam->nParam1, old_file, pParam->nParam3, new_file); 
} 

void sys_rmdir_processor(ST_PROCESS_INFO *pParam, pid_t pid, bool bClean) 
{ 
    RECORD_BEFORE_EXE(pid); 
    char file_path[MY_PATH_MAX] = { '\0' }; 
    read_string(pid, pParam->nParam1, file_path); 
    record_rmdir_syscall(pid, file_path); 
} 

void sys_unlink_processor(ST_PROCESS_INFO *pParam, pid_t pid, bool bClean) 
{ 
    RECORD_BEFORE_EXE(pid); 
    char file_path[MY_PATH_MAX] = { '\0' }; 
    read_string(pid, pParam->nParam1, file_path); 
    record_unlink_syscall(pid, file_path); 
} 

void sys_unlinkat_processor(ST_PROCESS_INFO *pParam, pid_t pid, bool bClean) 
{ 
    RECORD_BEFORE_EXE(pid); 
    char file_path[MY_PATH_MAX] = { '\0' }; 
    read_string(pid, pParam->nParam2, file_path); 
    record_unlinkat_syscall(pid, (int)pParam->nParam1, file_path); 
} 

int main(int argc, char *argv[]) 
{ 
    if (argc < 3) { 
        return -1; 
    } 

    pid_t pid_fock = 0; 
    switch (pid_fock = fork()) { 
    case -1: 
        break; 
    case 0: 
    { 
        //子进程 
        ptrace(PTRACE_TRACEME, 0, NULL, NULL); 
        //execvp(argv[2], &argv[2]); 
        //execvp(argv[1], &argv[1]); 
        execlp(argv[1], argv[1], NULL); 
        break; 
    } 
    default: 
    { 
        //父进程 
        int status      = 0; 
        //init(argv[2], argv[1]); 
        init(argv[1], argv[2]); 
        g_nPrograms                         = 0; 
        g_afuncProcessor64[SYS_execve]      = sys_execve_processor; 
        g_afuncProcessor32[11]              = sys_execve_processor; 
        g_afuncProcessor64[SYS_write]       = sys_write_processor; 
        g_afuncProcessor32[4]               = sys_write_processor; 
        g_afuncProcessor64[SYS_pwrite64]    = sys_pwrite64_processor; 
        g_afuncProcessor32[181]             = sys_pwrite64_processor; 
        g_afuncProcessor64[SYS_writev]      = sys_writev_processor; 
        g_afuncProcessor32[146]             = sys_writev_processor; 
        g_afuncProcessor64[SYS_pwritev]     = sys_pwritev_processor; 
        g_afuncProcessor32[334]             = sys_pwritev_processor; 
        g_afuncProcessor64[SYS_read]        = sys_read_processor; 
        g_afuncProcessor32[3]               = sys_read_processor; 
        g_afuncProcessor64[SYS_readv]       = sys_readv_processor; 
        g_afuncProcessor32[145]             = sys_readv_processor; 
        g_afuncProcessor64[SYS_preadv]      = sys_preadv_processor; 
        g_afuncProcessor32[333]             = sys_preadv_processor; 
        g_afuncProcessor64[SYS_pread64]     = sys_pread64_processor; 
        g_afuncProcessor32[180]             = sys_pread64_processor; 
        g_afuncProcessor64[SYS_readlink]    = sys_readlink_processor; 
        g_afuncProcessor32[85]              = sys_readlink_processor; 
        g_afuncProcessor64[SYS_readlinkat]  = sys_readlinkat_processor; 
        g_afuncProcessor32[305]             = sys_readlinkat_processor; 
        g_afuncProcessor64[SYS_mmap]        = sys_mmap_processor; 
        g_afuncProcessor32[192]             = sys_mmap_processor; 
        g_afuncProcessor64[SYS_rename]      = sys_rename_processor; 
        g_afuncProcessor32[38]              = sys_rename_processor; 
        g_afuncProcessor64[SYS_renameat]    = sys_renameat_processor; 
        g_afuncProcessor32[302]             = sys_renameat_processor; 
        g_afuncProcessor64[SYS_rmdir]       = sys_rmdir_processor; 
        g_afuncProcessor32[40]              = sys_rmdir_processor; 
        g_afuncProcessor64[SYS_unlink]      = sys_unlink_processor; 
        g_afuncProcessor32[10]              = sys_unlink_processor; 
        g_afuncProcessor64[SYS_unlinkat]    = sys_unlinkat_processor; 
        g_afuncProcessor32[301]             = sys_unlinkat_processor; 
        while (true) { 
            pid_t pid = wait3(&status, __WALL, NULL); 
            if (pid < 0 || pid >= 4194499) { 
                continue; 
            } 

            if (!g_bArrProcessInfo[pid]) { 
                g_nPrograms++; 
                g_bArrProcessInfo[pid] = true; 
                ptrace_trace_child(pid); 
                ptrace_continue(pid, 0); 
                activate(pid); 
                if (pid_fock == pid) { 
                    record_process_info(pid_fock); 
                } 
            } 
            // process exit 
            if (WIFEXITED(status) || WIFSIGNALED(status)) { 
                if (g_bArrProcessInfo[pid]) 
                    --g_nPrograms; 
                g_bArrProcessInfo[pid]      = false; 
                g_bArrProcessLogged[pid]    = false; 
                print_pid_write_record(pid); 
                remove_useless_rnode_by_pid(pid); 
                if (g_nPrograms > 0) { 
                    clean(pid); 
                    continue; 
                } 

                print_all_write_record(); 
                free_all_data(); 
                printf("Finished!\n"); 
                break; 
            } 

            if (WIFSTOPPED(status)) { 
                unsigned int event = (status >> 16); 
                unsigned int sig   = WSTOPSIG(status); 
                // trace exit: clean related context 
                if (event == PTRACE_EVENT_EXIT) { 
                    sys_execve_processor(NULL, pid, true); 
                    sys_write_processor(NULL, pid, true); 
                    sys_pwrite64_processor(NULL, pid, true); 
                    sys_writev_processor(NULL, pid, true); 
                    sys_pwritev_processor(NULL, pid, true); 
                    sys_read_processor(NULL, pid, true); 
                    sys_readv_processor(NULL, pid, true); 
                    sys_preadv_processor(NULL, pid, true); 
                    sys_pread64_processor(NULL, pid, true); 
                    sys_readlink_processor(NULL, pid, true); 
                    sys_readlinkat_processor(NULL, pid, true); 
                    sys_mmap_processor(NULL, pid, true); 
                    sys_rename_processor(NULL, pid, true); 
                    sys_renameat_processor(NULL, pid, true); 
                    sys_rmdir_processor(NULL, pid, true); 
                    sys_unlink_processor(NULL, pid, true); 
                    sys_unlinkat_processor(NULL, pid, true); 
                    if (!g_bArrProcessLogged[pid]) { 
                        record_process_info(pid); 
                        remove_useless_rnode_by_pid(pid); 
                    } 

                    ptrace_continue(pid, 0); 
                    continue; 
                } 
                //if ((event == PTRACE_EVENT_FORK) 
                // || (event == PTRACE_EVENT_VFORK) 
                // || (event == PTRACE_EVENT_CLONE) 
                // /*|| (event == PTRACE_EVENT_VFORK_DONE)*/) { 
                //    long child_pid  = ptrace_get_child(pid); 
                //    if(child_pid) { 
                //        //activate(child_pid); 
                //        //ptrace_trace_child(child_pid); 
                //     printf("parent: %d child: %d\n", child_pid); 
                //    } 
                //    //ptrace(PTRACE_ATTACH, child_pid, 0, 0); 
                //    //ptrace_continue(child_pid, 0); 
                //    //ptrace_continue(pid, sig); 
                //    //continue; 
                //} 
                // other signal, not syscall interrupt 
                if (0 == (sig & 0x80)) { 
                    if (5 != sig) 
                        ptrace_continue(pid, sig); 
                    else 
                        ptrace_continue(pid, 0); 
                        continue; 
                } 
                //printf("%d callid: %d %d\n", pid, stProcessInfo.bLinux32, stProcessInfo.nSysCallId); 
                ST_PROCESS_INFO stProcessInfo = { 0 }; 
                get_syscall_args(pid, &stProcessInfo); 
                if (stProcessInfo.nSysCallId >= 0) { 
                    if (!stProcessInfo.bLinux32) { 
                        // 64-bit process 
                        if (g_afuncProcessor64[stProcessInfo.nSysCallId]) 
                            g_afuncProcessor64[stProcessInfo.nSysCallId](&stProcessInfo, pid, false); 
                        } 
                        else { 
                            // 32-bit process 
                            if (g_afuncProcessor32[stProcessInfo.nSysCallId]) 
                                g_afuncProcessor32[stProcessInfo.nSysCallId](&stProcessInfo, pid, false);
                        } 
                    }
                    ptrace_continue(pid, 0); 
                    continue; 
                } 
                printf("strange status: %d!\n", status); 
                ptrace_continue(pid, 0); 
            } 
        } 
    } 

    return 0; 
} 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值