恶意代码分析实战 Lab 9-1 习题笔记

Lab 9-1

问题

1.如何让这个恶意代码安装自身?

解答: 这个既然开始了动态调试的部分,我们就只用OD来进行操作了,因为这个版本的恶意代码都是针对XP的,所以我打算是把OD装到XP那台运行代码的机器上

这里我们还是跟这书上的步骤走走,先摸清摸清一下套路

我们先打开OllyDbg,我这个是从官网下载的,原版的1.x的东西,然后大概是这样

图片

这里稍微说一下这个OllyDbg调整字体的方式

如果你用是吾爱破解版的话,字体应该没问题了(吾爱的插件比较多,懒得自己装插件的同学可以下一个)

图片

我们打开这里,然后点第一个这个Appearance这个东西

图片

就会打开一个这个界面,然后Height adjust这里可以调一下行高这个东西(第一个标黄的地方)

然后就是这个Change这个地方,这里可以调字体,把字体调成你喜欢的就行了

然后我们回归正题,继续按照书中的步骤一步一步调试

一般打开OD的时候,停在地方就是函数main开始的地方

我们可以看到我们停到了这里这个地方

图片

这里对我们来说并不是什么重要的东西,我们可以按F8(step-over)往后慢慢一步一步走,其实这时候按F7(step-into)是最保险的,因为你不知道什么时候会调用到函数,然后如果你想回退到开始执行的地方,可以按Ctrl+F2来回溯到函数一开始执行的地方

然后我们边按边对照这IDA的汇编代码看,不然光看OD的代码会把人看傻的

我们先来到第一个call的地方

图片

这里OD是标识了调用的是kernel32.GetVersion的函数

到现在这些函数都是程序执行前的初始化,这里是获取导入库的地址的

图片

然后我们继续往下,忽略那些有的没的函数,来到第二个调用CALL这里,这里具体是什么我们可以进去看看

进去我们可以发现这里其实也是代码的初始化过程

图片

这里是创建Heap也就堆,我们忽略,跳出来之后继续往下

图片

然后我们来到这个CALL的地方,这个也是初始化的东西,但是我们注意到下面这个CALL的函数是GetCommandLineA,这个函数是获得用户输入的函数,说明这个程序需要用户提供一个参数

然后我们继续往下会发现

图片

3915这个地方,然后这里有个CALL,我们按F7进去看看

进去发现这个也是初始化的过程

图片

调用了GetEnvironmentStrings这个函数来获取环境,这个函数为当前进程返回当前系统的环境变量

然后这个函数在里面一直比较什么,最后返回的eax是这样的

图片

EAX 00900768 ASCII "=::=::\"

这个对我们没什么用处

下面就是这三个函数的CALL

图片

我们进去第一个调用405E61处,然后我们会发现这个函数其实就是调用模块的

图片

我们退出,然后进入第二个调用

图片

这也是获得模块文件名字的调用

这是第三个调用,我们可以发现

图片

这里获得了当前进程的ID,这其实也是程序执行过程中,初始化的过程

然后我们继续往下分析

图片

这里有三个push,是Lab09-01.00402AF0调用的三个入参,右边的注释已经帮我们标出来了

后面我们分析会发现,其实这个调用Lab09-01.00402AF0就是main函数

然后我们就进入这个函数看看

然后进入就找到第一个调用,熟悉我的人都知道我喜欢找CALL来分析代码

图片

这是进入的第一个CALL,我们注意到这个CALL后面的一行的代码是

CMP DWORD PTR SS:[EBP+8], 1

这时候我们可以看看IDA

图片

IDA里面,标黄那个CALL下面的CMP是不是很眼熟

我们看上面的定义,argc=8,转换一下这个语句就是[ebp+8]

这就和OD对应上了

其实上面那个OD的第一个调用,就是__alloca_probe这个函数,这个函数是分配栈空间用的

于是我们就大概知道这是在比较你的有没有输入参数,如有有参数的话,argc就不会等于1,如果没有就是1

现在我们没有输入任何参数,然后[ebp+argc]其实=1,所以cmp相减之后,结果为0,于是ZF=1,则jnz不跳转

我们可以会到OD里面一步一步看看有没有跳转

的确,OD里面并没有跳转,继续往下执行了

图片

然后可能这时候有同学会问,这时候如果只看OD的数据,怎么知道DWORD PTR SS:[EBP+8]就是argc呢,如果你想这种硬分析的话,我们可以回到进入main函数之前

图片

这里在吊用Lab09-01.00402AF0之前push入栈了三个参数,这个函数就是我们标记为main的函数,学过C语言的同学都知道main函数的一般三个入参

int main(int argc, char **argv, char **envp)

图片

然后我们会发现这个main函数的三个入参都被OD标注了类型,然后还要我们清楚的一点就是这个每个参数的大小问题,不清楚的可以看看OD的标注,这里我们可以在Arg1Arg2这里看出来每个参数的大小是多少

Arg1的地址是DS:[40EB84],然后Arg2的地址是DS:[40EB80],从这里我们可以看出这个代码中,每个参数是占4个字节的大小

然后这里有个小的skill,在汇编函数调用中,我们压入这三个函数完之后,并不是马上就调用了这个函数,而是还要压入函数的返回地址

我们按照param3param2param1的次序依次在栈中压入参数,对应我们这里就是Arg3Arg2Arg1的次序

图片

然后在压参数结束之后,我们还要压入返回地址

图片

最后,准备调用函数的时候,再将ebp入栈,将esp的指赋值给ebp

图片

然后这时到了程序转移到被调用函数中执行,这个期间

地址ebp+0指向的是外面那个函数的ebp指针,ebp+4指向的是RET返回地址ebp+8就是我们的第一个入参,也就是这里的Arg1

所以这里为啥EBP+8对应argc就是这个道理

然后因为我们现在调试的时候并没有输入任何的入参,所以这里我们的argc是等于1

所以我们这里并不会跳转

图片

而是继续往下执行,然后从00402B03开始的话,就到了IDA的这里

图片

这里的Lab09-01.00401000对应的就是sub_401000,然后我们看看这个函数是干什么的

图片

这里调用了RegOpenKeyExA这个函数,然后OD已经帮我们标注好了这个入参的个个值,这里比IDA人性得很多

图片

这里是打开一个注册表的键,位置是HKEY_LOCAL_MACHINE这个地方,然后具体位置是在SOFTWARE\Microsoft \XPS这里

然后调用完之后就是测试这个调用是否成功了

图片

这里test了一下返回值,一般返回值是

图片

成功就返回0,假设调用成功了,and之后,结果为0ZF=1,那么je跳转

但是很不幸的是,我们OD调试的时候这个函数是返回失败的

图片

函数的返回值在EAX中存储,这时候是2

然后就是函数把eax置为0之后返回了,于是我们大概知道这个函数的一半是干什么的了

就是检测系统中是否存在那个注册表键,如果没有的话,就退出这个函数,并返回0,那如果成功呢,我们这里手动修改了一下eax看看

我们将这个ZF标志位手动置1

图片

然后执行

这里我们到了这个地方

图片

这里调用了一个注册表查询的函数,将名为Configuration的值查出来

这里调用完成之后我们手动将返回值也置为0,因为这时候这个键都不存在,哪里会查询成功

图片

然后就是一些返回值比较函数的作用,注意这里的ebp-4的位置,这里一般是属于临时空间

这里的JE其实和JZ是一样的,如果相等就跳转

这里其实如果调用失败之后,也会返回0

如果两个函数都调用成功,就返回1

然后这里我们为了节约分析时间,分析过后,其实这里的sub_401000如果调用失败,jz跳转之后,就会去执行一个Lab09-01.00402410的函数

图片

图片

其实也就是IDA中的sub_402410

图片

然后在这调用的最后,是这个

图片

这里用ShellExecuteA打开了cmd.exe,这应该是这个恶意程序的一种伪造,如果用户没有输入任何的参数,或者没有发现那个注册表项,再或者查询注册表失败,就来执行这个ShellExecuteA通过cmd.exe来运行一些命令

关键现在我们要找出这个命令是什么,然后我们现在回到这个函数的开始,开始我们的分析

图片

这里是开头的地方,一样的先是做了一些栈的初始化,将ebp压栈备份,然后把ebp指向esp

然后做完这些后把esp相减208,将esp往低地址的地方偏移了208个地址,4个地址存储一个字节,有52个字节的存储空间

这里稍微讲一下这个怎么看一个栈空间存储多少字节,一个EAX多少字节这个问题

图片

就是上图画圈圈这么一个空间占多少字节,你可能会说,这里只要看旁边的地址差是多少就知道了,对,但是并不是所有的栈都是图片中这样相差4

我们先看看一个栈空间可以存储多少的字节

图片

这是地址空间的OD里面的stack

这里的用4个地址空间的空间,存储了8个十六进制的数字,

然后一个字节等于2个十六进制字符,这里有8个,所以相当于4个字节,也就是一个地址指向一个字节,每个栈空间是4的地址差,为什么要用上面的208/4得出可以存储多少个数据节,就是这么来的

一个栈的空间,刚好可以存储进去一个EAX或者EBX(因为也是8位十六进制的,4字节)

我们继续回到汇编代码中

图片

这里一共分配了52个空间了,然后下面push了四个参数进入栈中,这四个参数并不是占这些分配的空间,他们是在esp的基础上往低地址的地方堆砌,注意,这里是从esp开始,而不是ebp

图片

然后最后通过

LEA EAX, DWORD PTR SS:[EBP-208]

将通过sub esp, 208后的esp值返回给eax

图片

IDA中已经帮我们把这个变量标注为了Filename这个名称,然后这个代码片段到最后的就是删除那个可执行程序本身(刚刚写了一下午的分析,,,一关全没了,,,所以这里简写了。。。)

其实之类这些各种比较字符串长度的操作,主要是为了在赋值字符串的时候,不覆盖原来的字符串,所以才要求计算偏移也就是字符串的长度,这点看懂了这部分基本没啥问题了,最后那个用cmd.exe执行的指令,有个入参就是这个拼接后的字符串


如果我们手动修改这个cmp的结果值

图片

让这个程序以为我们输入了一个参数,然后看看

图片

然后这里将我们的argv(也就是ebp+8)赋值给eax,然后就是把argv(也就是ebp+c)赋值给了ecx,然后这个代码

MOV EDX, DWORD PTR DS:[ECX+EAX*4-4]

的作用就是取argv里面的最后一个参数的值,放入edx中又放入eax

然后就开始调用这个Lab09-01.00402510的这个函数

也就是IDA中的sub_402510这个

图片

进来之后

图片

前面的两句是函数栈的初始化

2515处的地方,将我们的获取到的argv的最后一个函数(ebp+8)赋值给了edi这个地方

下一句将ecx置为-1

然后repne scas edi这个其实是计算edi指向的字符串的长度

然后not ecx是全部长度,包括字符串最后的结束符\0

然后下面的ecx + -1就是去除这个结束符之后的长度

最后将这个长度和4进行比较,这里剧透一下,如果长度不是4,函数就直接返回0

我们这里肯定不能让他就这么返回了,改~

图片

这里的意思就是将那个字符的第一个字符,通过指针赋值给了cl,然后又给了edx

然后最后和61比较,61其实是aASCII编码

然后我们这里也是改~

让他一直执行下去

图片

这是将那个字符串的第二个字符(eax+1)赋值给了cl,然后这里的代码算是比较复杂的了,主要集中在这里

SUB AL, BYTE PTR DS:[EDX]

al是字符串的第二个字符,[edx]是字符串的第一个字符,如果能想清这里,这里就理解了

这里的意思是,如果字符串的第二个字符,比第一个字符大1(也就是第二个字符如果是b),就跳转继续,否则和上面一样,直接返回0(这里说的字符串,是指main入参的argv最后一个参数)

然后下面就是这个代码

图片

这里的[ebp-4]是字符串的上面字符串相减之后的值,是上一个代码片段遗传下来的

这里的IMUL DL意思就是将dlal相乘,然后将结果放在ax中(因为ax的一半就是al,而eax1/4就是al)

因为al上一代码片段曾经计算过了,是0x01h这个值,所以最后和dl相乘之后,就还是dl的值,也就是63

这里注意考虑[ebp-4]的时候,和上下文结合起来看

这段代码的意思就是将第三个字符和c(也就是63)比较

这里其实是要注意这个MOVSX这个指令,这个指令是带符合操作的

然后下一个字符

图片

这里的al这时候是63,上一步计算后的结果最后还存在al中,于是al=1之后就是64也就是ASCIId

然后这些计算都通过之后,就会将eax赋值为1然后返回了

这里的代码有点变态,因为各个参数之间不是独立的,后面的比较参数是根据前面的计算结果来的,如果写出伪C代码的话,大概如下

int sub_402510(const char *argv_last_string)
{
    char *ptmp = NULL;
    len_argv_string = strlen(argv_last_string);
    if (len_argv_string != 4)
    {
        return 0;
    }

    ptmp = &argv_last_string[0];
    int a_value = 61;
    if (*ptmp != a_value)
    {
        return 0;
    }

    int ntmp = (int)(*(ptmp+1)) - a_value;
    if (ntmp != 1)
    {
        return 0;
    }

    int c_value = 63 * ntmp;
    if (*(ptmp + 2) != c_value)
    {
        return 0;
    }

    int d_value = c_value + 1;
    if (*(ptmp + 3) != d_value)
    {
        return 0;
    }

    return 1;
}

大概就是上面这个样子的一个函数,很变态,不是直接比较a,b,c,d,而是根据前一个计算的结果推倒后面的要比较的字符串

然后函数出来之后就是一个检测返回值是否是1

图片

然后下面的地方是对应IDA的这里

图片

这个argv如果你输入的是abcd话就是abcd,如果没输入就是这个可执行文件的绝对路径

然后我们这里看见这个函数__mbscmp,这个函数在MSDN中的解释就是

图片

比较字符串的东西,这个函数其实和strcmp差不多

图片

所以这里2B56地址的这个函数其实就是__mbscmp,我们这里可以标记一下

然后我们看看他push进去的参数有哪些

图片

第一个push进去的是byte_40C170,这个好像是个字符串的,第二个push进去的是eax

图片

图片

很可惜的就是IDA中并没有很明确的标注这个参数,IDA这里的2Dh并没有翻译成ASCII2Dh就是-

这里第一个push的参数我们知道了,是-in,然后我们看看第二个push的参数是什么

图片

这里将[EBP-1820]位置的值赋值给了eax

而这个[EBP-1820]是从edx来的

图片

EDX是从[ECX+4]来的,这里注意一下就是[ECX+4]这里读数据的时候要从倒着读,这里的[ECX=4]=00900AE4

图片

图片

内存中是380B9000但是在栈中是00900B38这样

然后现在我们也知道了第二个参数是abcd,这里是将abcd这个字符串和-in进行比较

这里因为我们现在是不等于的,所以返回值不会为0,这里我们返回了1

图片

testand逻辑的运算,and之后还是1ZF=0JNZ就会跳转

图片

就会跳右边这条绿线

然后这里的结构其实和上面这里是一样的

图片

也是比较这个入参和-re是否相同,我们这里依旧不相同

我们还是顺着绿色这个线继续走

图片

下面的这里还是一个test比较之后JNZ跳转

图片

这里是比较是否和-c相同,我们依旧不相同,然后我们继续往下

图片

然后我们这里走的是左边这条绿色的线

这里和上面一样,是比较-cc选项的

我们依旧不等于,然后继续走jnz绿线

图片

到这里我们就会调用这个sub_402410的函数

然后这个函数我们上面曾经遇到过,就是没输入任何密码的时候调用的那个删除自身函数

图片

这条线走完之后我们去看看那些个分支是干什么的

一开始是如果我们输入了参数abcd之后,第一个比较的是-in选项,我们进去看看是什么

图片

如果当时输入了-in这个参数,就会跳到这里,对应IDA就是

图片

这里的[EBP+8]其实就是调用这个函数时的输入也就是argc这个参数

这里会比较参数的个数是否为3,如果相等的话,ZF=1,就走红线,如果不相等就走绿线

我们先看红线是啥

相等之后就会调用这些函数

图片

对应IDA也就是这里

图片

这里我们可以结合这IDA来看,IDA已经表明了这个ecx是将ServiceName赋值给它

我们可以看看这个值是多少

图片

IDA中标识是一段地址空间,我们这时候就可以看OD的了

OD里面标识是[EBP-404]的地址上的值,计算出来就是0012FB7C也就是

这里就相当于sub_4025B0(0012FB7C, 400)这样的函数调用,然后我们进去这个函数看看

图片

这里我们注意到有个调用是GetModuleFileNameA这个函数,这个函数会将返回值放在OD标注的PathBuffer这里,所以我们查看返回值要查看这个地址上的值是多少

这里的PathBuffer是=0012E344

我们查看一下就是这样的

图片

也就是我们当前这个可执行文件的绝对路径

然后就是一个test

图片

这里如果成功是返回这个绝对路径的长度,这里test之后,ZF=0,所以jnz会跳转过去

跳转过来就是这样的函数

图片

这里有个调用函数

IDA中标注的是

图片

也就是__splitpath这个函数

这个函数在MSDN里面貌似查不到,我们直接看执行结果是什么

最后我们可以得出调用顺序是这样的__splitpath(0012E344, 0, 0, 0012FB7C, 0)

其中0012E344这个地址是存着上一部返回的绝对路径

图片

这个函数执行之后的寄存器变化

图片

可以看出来这个函数的返回值EAX貌似是个地址,或者说就是个指针,但是这个指针指向的地址都是乱码,这个不知道是什么东西

图片

然后我们看看传进去的两个入参,发现,函数执行之后,0012FB7C原本没有值的,现在存着这个感觉是像被分割之后的字符串

图片

值是Lab09-01,注意我们要看\00字符串结束符,所以我们大概知道了这个函数就将那个绝对路径分割成为Lab09-01这个字符串

然后这个函数如果调用成功返回的是0,失败返回1

图片

然后我们从函数中返回之后就是来到了这里,这有个test,我们成功了,所以返回值是0,然后test之后ZF=0JE跳转

如果这里不是返回0,那么整个这个函数会跳转之后结束并返回-1

跳转之后来到这里

图片

OD中是这样的

图片

这里有调用一个函数,还是按上面那种写法就是这样的sub_402600(edx),我们可以执行OD来看看这个EDX的值是多少

图片

我们可以大概看出来,这个EDX其实就是刚刚上面那个函数处理后的返回结果Lab09-01

函数进来之后的是这样的(这个函数很大)

图片

对应IDA中就是这样的

图片

现在我是处于判断-in成功之后那条线进入的一个函数

这里的call __alloca_probe就是函数初始化栈的东西,这个我们可以不用管

图片

这个写简单点就是sub_4025B0(EAX, 400)(这个函数就是那个__split什么那个函数)

这里函数就有这么几个入参,执行看看

然后这时候的EAX返回值就是0

也就是这里

图片

这里的eax=0,然后test之后ZF=1,然后JZ就绿线跳转了,红线是结束这个函数并返回1

绿线之后就是来到这里

图片

对应IDA的这里

图片

这里有个字符串我们注意到%SYSTEMROOT%\\system32\\

图片

这里显示是的计算[EDI]的字符长度也就是上面那个字符串的长度

这么一串指令到最后的

图片

都是为了调用OpenSCManagerA这个函数,这个函数是在指定的计算机上建立与服务控制管理器的连接,并打开指定的服务控制管理器数据库。

前两个参数为0说明这个服务控制器是在本地上

这时候的寄存器

图片

可以看出上面那个操作就是把这个字符串拼接成EDX所示的样子

然后这里将返回值放在[EBP-404]里面

然后和0比较,如果返回值EAX等于0ZF=1JNZ不跳转,继续执行就是结束并返回1

图片

然后我们这里并不等于0,于是跳转

也就是来到这里

图片

OD的这里

图片

这里的第一个push入栈的eaxIDA中标注是lpServiceName,然后第二个push入栈的ecxhSCManager

图片

这里的eax的值是Lab09-01,也就是要打开的服务的名称,如果调用成功,返回一个指针

然后我们这里是第一次调用这个函数,所以这个函数会返回0作为返回值

0执行cmp之后ZF=1,则JE跳转

跳转

执行之后就会跳转到绿色的线那里,如果调用成功了,就重新配置一下这个服务的参数

图片

然后这个函数就是创建一个叫Lab09-01的服务

这里调用成功之后,返回值肯定不为0,所以和0执行cmp之后,ZF=0,之后JNZ肯定就会跳转了

图片

跳转之后的绿线就是执行一写关闭操作,来关闭打开的Handle

之后就是调用了

图片

这里的入参我们可在在OD里面查看

这里的入参edx等于

图片

然后我们看看调用后的结果,存放在ecx指向的地址

图片

这里把%SYSTEMROOT%这个变量替换成了环境变量然后就成了0012DF44这个地址上的字符串

调用成功之后继续往下走

图片

我们直接看看这个入参和结果

这是执行之后的返回值,存在eax指向的地址

图片

然后执行成功之后继续往下

图片

这里两个入参,edxecx

图片

lpNewFileName的值是ecx,然后lpExistingFileName也就是edx也是上面我们刚刚的返回值

这里要将这个可执行文件赋值到system32下面

执行成功之后就会跳到这里

图片

我们看看这个函数的入参是多少

也就是这个system32下面的可执行文件

图片

进入这个函数之后,我们第一个要执行的函数是这个

图片

这个函数在MSDN里面的解释就是检索系统目录的路径。 系统目录包含系统文件,如动态链接库和驱动程序。

我们看看返回值,返回值eax是字符长度,真正的字符被赋值到了lpbuf里面

lpBuffer的值是

图片

这是返回值

图片

如果函数返回成功了,就会跳转到绿线继续执行,否则返回1

图片

调用sub_4014E0这个函数的时候,入参是ecxeax

图片

然后就调用了sub_4014E0

进入这个函数之后是这样的

图片

我们还是按照先看入参然后看返回值的做法看看

这个函数的入参是eax,这个的值是

图片

这也就是创建这个文件叫C:\WINDOWS\system32\kernel32.dll

这里我们执行后会发现返回值不是0,然后就是和0就行cmpZF=0,则JNZ跳转,走绿线

也就是这里

图片

我们还是用OD来看参数,这个函数的意思就是检索文件或目录创建,上次访问和上次修改的日期和时间。

我们看看参数

图片

hfile的值是5c也就是上面那个函数的返回值

然后这个函数会把返回值放到lpCreationTimelpLastAccessTimelpLastWriteTime这里

我们看看CreationTime的返回值好像是乱码的东西

调用成功之后就会调用这个

图片

这里先关闭上面那个Handle

然后就是CreateFileA

入参ecx的值

图片

然后设置这个文件的时间和kernel32.dll的一样

然后就是关闭这个Handle然后返回

这个函数成功返回0,失败返回1

然后接着上面一层这个函数也会返回,从这里返回之后就会来到这里

图片

这里开始调用sub_401070这个函数

这里入参有个网址,我们进去这个函数看看

这个函数的一连串的指令之后是这个函数

图片

这里看函数名是创建一个注册表的键值

是在HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft \\XPS这里创建

图片

然后设置一个键叫Configuration

然后就是在关闭之后返回

然后上面一层的函数也会返回,这里成功返回0,失败返回1

现在我们知道了这个-in参数会干什么了,这个参数会让函数安装自己为一个服务,然后把Lab09-01.exe复制到system32下面

我们试试下一个参数-re这个参数

图片

这里我们将入参设置为-re

然后来到OD的这里

图片

然后这里相同之后,ZF=1JNZ不会跳转,走红线

然后这里会比较我们的入参是不是三个

我们的入参就是三个,所以这里走红线

然后就会来到这里

图片

然后这里有个函数sub_4025B0这个东东

进去看看就会发现这个

我们进去看看

图片

GetModuleFileNameA函数的返回值是

图片

然后就开始退出这个函数了

图片

然后就是分离出这个值之后返回


2.这个恶意代码的命令行选项是什么?它要求的密码是什么?

解答: 这个代码分析太长时间了哈哈哈,密码是abcd


3.如何利用OllyDbg永久修补这个恶意代码,使其不需要指定的命令行密码?

解答: 修改特定的地址上的代码,然后不跳转就ok


4.这个恶意代码基于系统的特征是什么?

解答: 恶意代码创建了一个注册表项,然后一个名为XYZ的服务


5.这个恶意代码通过网络执行了哪些不同操作?

解答: SLEEP, UPLOAD, DOWNLOAD, CMDNOTHING之类的指令


6.这个恶意代码是否有网络特征?

解答: 有,对对应网址的资源有个一个GET请求


本文完

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值