int3和SingleStep联合实现Linux系统键盘监控(kprobe原理)

霍尔电流传感器在新能源汽车中的应用综述 本文旨在全面剖析霍尔电流传感器在新能源汽车领域的应用现状、技术挑战及发展趋势。通过系统梳理其在电池管理系统、电机控制、车载充电等核心系统的应用场景,深入分析面临的技术瓶颈与创新解决方案,并基于最新技术动向展望未来发展方向,为相关领域的技术创新提供参考依据。 阅读详情

就着Linux系统键盘监控这个话题,再写点什么。

前面已经写了三篇,可以一起汇总着看,算是一个简单的一题多解吧:

以上所有的这些,都不是Linux内核标准的做法,如果把这些看作一些奇技淫巧的话,Linux内核标准的做法则是另一种把戏:

  • int3结合debug寄存器单步执行。

是的,这就是Linux内核kprobe的方法。一个完整的kprobe闭环如下图所示:
在这里插入图片描述【绝大多数文章都没有说清楚kprobe的原理,只是单纯的源码分析】

按照这个逻辑, inline hook可以hook 任意位置,任意指令长度的代码了。

对于键盘监控,按照上述原理,下面是代码:

// int3debughook.c
#include <linux/module.h>
#include <linux/kdebug.h>
#include <linux/kallsyms.h>
#include <linux/tty.h>

#define DIE_INT3	2
unsigned long orig, old;

void inst(void)
{
	asm ("nop;");
}

int int3_notify(struct notifier_block *self,
					   unsigned long val,void* data)
{
	struct die_args *args = data;
	struct pt_regs *regs = args->regs;
	int ret = NOTIFY_DONE;

	switch(val){
	case DIE_INT3:
	{
		struct tty_struct *tty;
		char c;

		// 按照x86_64传参规则,从rdi中取出参数1,从rsi中取参数2
		tty = (struct tty_struct *)regs->di;
		c = regs->si;
		printk("debug %c  %s\n", c, tty->name);

		// 保留原始IP寄存器
		old = regs->ip;
		// 新的IP设置为被int3替换的指令
		regs->ip = (unsigned long)inst;
		// 设置调试寄存器标志,开启单步
		regs->flags |= X86_EFLAGS_TF;
		regs->flags &= ~X86_EFLAGS_IF;

		ret = NOTIFY_STOP;
		break;
	}
	case DIE_DEBUG:
	{
		// 关闭单步模式
		regs->flags &= ~X86_EFLAGS_TF;
		// 恢复原始指令
		regs->ip = old;
		ret = NOTIFY_STOP;
		break;
	}
	default:
		break;
	}

	return ret;
}

static struct notifier_block int3_nb = {
	.notifier_call = int3_notify,
	.priority =0x7fffffff,
};

unsigned char *p;
unsigned long cr0;
static int __init int3debug_hook_init(void)
{
    int ret;

	ret = register_die_notifier(&int3_nb);
	if (ret) {
		printk("register_die_notifier failed %d\n", ret);
		return ret;
    }

	orig = (unsigned long)kallsyms_lookup_name("n_tty_receive_char");

	p = (unsigned char *)orig;
	cr0 = read_cr0();
	clear_bit(16, &cr0);
	write_cr0(cr0);
	*(char *)inst = *p;
	memset(p, 0xcc, 1);
	set_bit(16, &cr0);
	write_cr0(cr0);

	return 0;
}

static void __exit int3debug_hook_exit(void)
{
	cr0 = read_cr0();
	clear_bit(16, &cr0);
	write_cr0(cr0);
	memset(p, *(char *)inst, 1);
	set_bit(16, &cr0);
	write_cr0(cr0);
	unregister_die_notifier(&int3_nb);
}

module_init(int3debug_hook_init)
module_exit(int3debug_hook_exit)
MODULE_LICENSE("GPL");

下面是效果:

[root@localhost probe]# insmod ./int3debughook.ko
[root@localhost probe]# dmesg
[ 3187.284028] debug d  pts0
[ 3187.419775] debug m  pts0
[ 3187.556342] debug e  pts0
[ 3188.372819] debug s  pts0
[ 3188.587878] debug 	  pts0
  pts0.204373] debug

其实,上述代码中,我所不规范的是,printk应该封装在一个独立的handler中,所以,case DIE_INT3应该是:

handler(...)
{
	SAVE_ALL_REGS;
	do_some_thing;
	RESTORE_ALL_REGS;
}

...
case DIE_INT3:
{
	// 保留原始IP寄存器
	old = regs->ip;
	// 新的IP设置为handler
	regs->ip = (unsigned long)handler;
	// 暂不开启单步
	break;

嗯,kprobe差不多也就是这么回事,封装更好而已。


浙江温州皮鞋湿,下雨进水不会胖。

【机器学习|评价指标8】混淆矩阵(Confusion Matrix)、Cohen‘s Kappa 系数、详解,平均交并比(mIoU)、列联表(Contingency Table)附代码。 【机器学习|评价指标8】混淆矩阵(Confusion Matrix)、Cohen's Kappa 系数、详解,平均交并比(mIoU)、列联表(Contingency Table)附代码。 阅读详情

相关推荐

Python量化投资入门教程:从零构建你的第一个交易策略

量化投资教程以数学模型和计算机程序实现超额收益,选用Python工具链完成数据获取、策略构建及回测。通过移动平均线交叉策略示例,展示数据预处理、信号生成、收益计算及可视化全流程,结合收益率、最大回撤和夏普比率评估策略,并建议参数优化与策略融合提升稳健性。

ylmx5201314的博客 2448

手工处理int3实时监控Linux系统键盘输入

上周写了一篇Linux系统监控键盘输入的文字: https://blog.csdn.net/dog250/article/details/106425811 事后想了下,能不能不用标准的5字节32位相对地址跳转实现inline hook,换一种方法实现呢? 嗯,换int3实现! stap就是int3实现的,但是那毕竟是现成的工具,我需要一种手艺人的方法。 我们知道,n_tty_receive_char函数实属有符号的inline,它并非FTRACE NOP开头的标准函数,所以本来就很难用5字节替换来inli

TCP/IP 1万+

魔百和CM211-1刷Armbian全流程:从安卓盒子到Linux服务器的完美变身

本文详细介绍了如何将魔百和CM211-1机顶盒刷入Armbian系统,实现从安卓盒子到Linux服务器的完美变身。涵盖硬件兼容性验证、固件选择、刷机流程、系统优化及常见问题解决,特别适合技术爱好者低成本搭建家庭NAS或开发环境。

weixin_30329623的博客 232

kprobe实现原理解析

一、简介 kprobe是内核的动态探测工具,几乎可以探测任何一条内核指令。kprobe根据探测点类型可分为三种: kprobes, jprobes和kretprobes (也叫返回探测点)。 kprobes是可以被插入到内核的任何指令位置的探测点,jprobes则只能被插入到一个内核函数的入口,而kretprobes则是在指定的内核函数返回时才被执行。 kprobe...

hjkfcz的博客 3485

Simulink实战:5步搞定固定翼飞行器六自由度动力学建模(附模型下载)

本文提供了一份详细的Simulink实战指南,通过5个核心步骤手把手教你构建固定翼飞行器的六自由度动力学模型。内容涵盖从顶层架构设计、核心方程手动实现,到模型集成、测试调试及进阶优化的完整流程,旨在帮助航空航天学生和工程师将抽象理论转化为可运行的仿真模型,并附有模型下载。

perl8的博客 626

【重启后nvidia-smi 命令出现错误】

NVIDIA-SMI has failed because it couldn't communicate with the NVIDIA driver. Make sure that the latest NVIDIA driver is installed and running.

qq_44769377的博客 5203

Linux内核调试技术之kprobes:基本原理与使用

Linux kprobes技术是一种可以跟踪内核函数执行状态的轻量级内核调试技术,利用kprobes技术可以在运行的内核中动态的插入探测点,当内核运行到该探测点后可以执行用户预定义的回调函数,以收集所需的调试状态信息而基本不影响内核原有的执行流程。{return 0;}.symbol_name = "do_fork", // 要追踪的内核函数为 do_fork.pre_handler = handler_pre // pre_handler 回调函数。

Aspiresky的专栏 3161

双重分组进位跳跃进位链

跳跃进位链

qq_62260432的博客 934

kprobe 原理详细分析

内核调试机制 kprobe 原理详细分析,使用 kprobe 可以在内核任意位置设置 hook 并跟踪调试

看我眼色行事的博客 1640

融合双主干网络提升YOLOv8性能一种基于SOTA思想的目标检测改进

YOLOv8采用了单一的主干网络进行特征提取,通常是基于CSPDarknet作为骨干网络,在目标检测过程中通过不同的卷积层提取不同尺度的信息。虽然这种设计在大多数应用场景下表现出色,但在面对复杂背景、细节丰富的物体或小物体检测时,YOLOv8有时难以保持良好的精度。在本文中,我们提出了一种基于双主干网络的YOLOv8改进方案,通过结合先进的SOTA思想,全面提升了YOLOv8在目标检测任务中的表现。双主干网络架构。

博客 1149

华为OD新系统机试真题-勇攀数字高峰(Java/Py/C/C++/Js/Go)

新系统 华为OD机试新系统【勇登数字高峰】,提供C语言、Java、C++、Python、Go、JavaScript源码实现,并提供完整思路讲解,以及在线OJ模拟做题。华为OD新系统机试真题【勇登数字高峰】

qq_45776114的博客 1530

Live555-SingleStep函数分析

Live555-SingleStep函数分析

xiangxiang2017的博客 916

高德地图API如何使用

通过以上步骤,您可以成功接入并使用高德地图API来为您的应用添加丰富的地图功能。

QQ_778132974的博客 4769

Linux内核 eBPF基础:kprobe原理源码分析:基本介绍与使用示例

Linux内核 eBPF基础 kprobe原理源码分析:基本介绍与使用 荣涛 2021年5月11日

RToax 8404

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain&#39;s apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath(&#39;DeepLearnToolbox&#39;));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand(&#39;state&#39;,0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W&#39;); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand(&#39;state&#39;,0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = &#39;sigm&#39;;%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, &#39;Too big error&#39;);```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand(&#39;state&#39;,0)sae = saesetup([784 100]);sae.ae{1}.activation_function = &#39;sigm&#39;;sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)&#39;)% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = &#39;sigm&#39;;nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, &#39;Too big error&#39;);```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x&#39;,28,28,60000))/255;test_x = double(reshape(test_x&#39;,28,28,10000))/255;train_y = double(train_y&#39;);test_y = double(test_y&#39;);%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you&#39;ll get around 1.2% errorrand(&#39;state&#39;,0)cnn.layers = { struct(&#39;type&#39;, &#39;i&#39;) %input layer struct(&#39;type&#39;, &#39;c&#39;, &#39;outputmaps&#39;, 6, &#39;kernelsize&#39;, 5) %convolution layer struct(&#39;type&#39;, &#39;s&#39;, &#39;scale&#39;, 2) %sub sampling layer struct(&#39;type&#39;, &#39;c&#39;, &#39;outputmaps&#39;, 12, &#39;kernelsize&#39;, 5) %convolution layer struct(&#39;type&#39;, &#39;s&#39;, &#39;scale&#39;, 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, &#39;Too big error&#39;);```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand(&#39;state&#39;,0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, &#39;Too big error&#39;);%% ex2 neural net with L2 weight decayrand(&#39;state&#39;,0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, &#39;Too big error&#39;);%% ex3 neural net with dropoutrand(&#39;state&#39;,0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, &#39;Too big error&#39;);%% ex4 neural net with sigmoid activation functionrand(&#39;state&#39;,0)nn = nnsetup([784 100 10]);nn.activation_function = &#39;sigm&#39;; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, &#39;Too big error&#39;);%% ex5 plotting functionalityrand(&#39;state&#39;,0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = &#39;softmax&#39;; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, &#39;Too big error&#39;);%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand(&#39;state&#39;,0)nn = nnsetup([784 20 10]); nn.output = &#39;softmax&#39;; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, &#39;Too big error&#39;);```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

Linux kprobe原理

Linux kprobe原理,以及其优化简介

小立仔 3974

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

Linux 下的一个全新的性能测量和调式诊断工具 Systemtap,第 1 部分: kprobe

http://www.ibm.com/developerworks/cn/linux/l-cn-systemtap1/index.html kprobe 的原理、编程接口、局限性和使用注意事项 杨 燚 (yang.y.yi@gmail.com), 计算机科学硕士, Intel 简介: 本系列文章详细地介绍了一个Linux下的全新的调式、诊断和性能测量工具Systemtap和它所依

hnllei的专栏 1605

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

[monitor] 8. Linux kprobe(内核探针)

1、kprobe概念kprobe是一个动态地收集调试和性能信息的工具,它从Dprobe项目派生而来,是一种非破坏性工具,用户用它几乎可以跟踪任何函数或被执行的指令以及一些异步事件(如timer)。它的基本工作机制是:用户指定一个探测点,并把一个用户定义的处理函数关联到该探测点,当内核执行到该探测点时,相应的关联函数被执行,然后继续执行正常的代码路径。kprobe实现了三种类型的探测点: kprobe

pwl999的博客 4279

x86_64平台SingleStep单步调试原理和示例

先看一个程序: // simple.c int value = 0; int main(int argc, char **argv) { value ++; value ++; value ++; value ++; value ++; value ++; } 现在想单步调试它,跟踪value的变化,如何来做? 用gdb即可。但是如果想要理解背后发生了什么,还是要手工来一遍效果才更佳。 这就需要理解单步跟踪的本质(Linux为例): x86_64体系结构,FLAGS寄存器有一个TF标志位来使

TCP/IP 4969

Live555-基类研究二

TaskSecheduler类是一个任务调度器,它是整个Live555的任务调度中心,程序在任何时刻的任何动作,均由该类完成。其核心函数是SingleStep。Live555的任务主要分为Delayed Event、Socket Event以及Trigger Event。其类层次结构如下图所示:   从类图中可以看到,其类层次结构和第二节中的UsageEnvironment类极其相

BIGBurning 2270

内核调测工具kprobe之原理篇

上篇文章我们讲了Kprobe的用法,这次我们一起看下其实现的原理。在上次的模块例子中插入dump_stack函数,获得调用栈的情况,根据栈来反推其调用流程:Calltrace: [&lt...

Peter的专栏 1449

kprobe原理解析(二)

上一篇文章和大家简要说明了下kprobe到底应该怎样用,那么现在我们就揭开kprobe神秘的面纱,刨根问底,一睹kprobe的庐山真面目。 kprobe的工作过程大致如下: 1)注册kprobe。注册的每个kprobe对应一个kprobe结构体,该结构中记录着插入点(位置),以及该插入点本来对应的指令original_opcode; 2)替换原有指令。使能kprobe的时候,将插入点位置的指...

weixin_30650039的博客 315

Linux kprobe的使用

Linux kprobe的简单使用

小立仔 5331

kprobe钩子详细介绍

Kprobe钩子介绍 kprobe是一个动态地收集调试和性能信息的工具,它从Dprobe项目派生而来,是一种非破坏性工具,用户用它几乎可以跟踪任何函数或被执行的指令以及一些异步事件(如timer)。它的基本工作机制是:用户指定一个探测点,并把一个用户定义的处理函数关联到该探测点,当内核执行到该探测点时,相应的关联函数被执行,然后继续执行正常的代码路径。 kprobe实现了三种类型的探测点:kp

kfy2011的专栏 4727

分析kprobe原理

Linux kprobe原理分析

greenandblue的专栏 745

【原创】Linux kprobe 实现原理 图文详解 (一) kprobe实现原理

图文详解 linux kprobe 底层实现原理

h_b__k的博客 2108

Kprobe实现原理

上面这个函数我们可以看到,会调用kprobe的pre_handler和post_handler,这两个函数被我们实现好,装填进kprobe,并用register_kprobe注册到kprobe子系统;kprobe其实就是将某个要检测的指令备份,再替换成int3(x86)或者未定义指令(arm)来触发异常,再调用对应体系的异常处理函数来执行我们自定义的hook,执行完我们自定义的hook,再将备份的指令放回原来的位置继续往下执行。替换成未定义指令后,就会触发的未定义指令异常处理函数。

我的博客 574
上一篇: x86_64平台SingleStep单步调试原理和示例
下一篇: Linux系统调试之return probe原理和示例
dog250
dog250 领域专家: 操作系统技术领域 领域专家: 操作系统技术领域
博客等级 码龄19年 3万+粉丝 2516原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值