加密壳

5、加壳工具初始化PE文件信息

1、课程目标

  1. 设计加壳工具的架构
  2. 实现PE文件的读取和解析
  3. 收集加壳所需的信息
  4. 验证目标PE文件的有效性

2、名词解释

名词 全称 解释
Packer 加壳器 对PE进行加壳的工具
Target PE 目标PE 要被加壳的原始文件
Stub 存根/壳 加载原始代码的代码段
PE Info PE信息 目标PE的结构信息

3、使用工具

  • Visual Studio 2022
  • CFF Explorer
  • PE-bear

4、技术原理

4.1、加壳工具架构

+------------------+
|   命令行参数解析  |
+------------------+
         |
         v
+------------------+
|   加载目标PE     |
+------------------+
         |
         v
+------------------+
|   解析PE信息     |
+------------------+
         |
         v
+------------------+
|   加载壳代码     |
+------------------+
         |
         v
+------------------+
|   加密原始代码   |
+------------------+
         |
         v
+------------------+
|   修复壳重定位   |
+------------------+
         |
         v
+------------------+
|   构建新PE      |
+------------------+
         |
         v
+------------------+
|   保存输出文件   |
+------------------+

5、代码实现

5.1、PE信息结构

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

// PE文件信息结构
typedef struct _PE_FILE_INFO {
    // 文件数据
    LPBYTE      fileData;
    DWORD       fileSize;
    
    // PE头指针
    PIMAGE_DOS_HEADER       dosHeader;
    PIMAGE_NT_HEADERS32     ntHeaders32;
    PIMAGE_NT_HEADERS64     ntHeaders64;
    PIMAGE_FILE_HEADER      fileHeader;
    PIMAGE_SECTION_HEADER   sectionHeaders;
    
    // 基本信息
    BOOL        is64Bit;
    BOOL        isDll;
    WORD        machine;
    WORD        sectionCount;
    DWORD       headerSize;
    
    // 可选头信息
    ULONGLONG   imageBase;
    DWORD       entryPoint;
    DWORD       sizeOfImage;
    DWORD       sectionAlignment;
    DWORD       fileAlignment;
    WORD        subsystem;
    WORD        dllCharacteristics;
    
    // 数据目录
    PIMAGE_DATA_DIRECTORY   dataDirectories;
    DWORD                   dataDirectoryCount;
    
    // 区段信息汇总
    struct {
        char    name[9];
        DWORD   virtualAddress;
        DWORD   virtualSize;
        DWORD   rawAddress;
        DWORD   rawSize;
        DWORD   characteristics;
    } sections[32];
    
} PE_FILE_INFO, *PPE_FILE_INFO;

// 加壳器上下文
typedef struct _PACKER_CONTEXT {
    PE_FILE_INFO    targetPE;       // 目标PE信息
    PE_FILE_INFO    stubPE;         // 壳PE信息
    
    // 壳代码
    LPBYTE          stubCode;
    DWORD           stubCodeSize;
    
    // 加密配置
    DWORD           encryptionType;
    DWORD           encryptionKey;
    
    // 输出配置
    char            outputPath[MAX_PATH];
    DWORD           flags;
    
} PACKER_CONTEXT, *PPACKER_CONTEXT;

5.2、加载和解析PE文件

// 加载文件到内存
BOOL LoadFileToMemory(const char* filePath, LPBYTE* data, DWORD* size) {
    HANDLE hFile = CreateFileA(
        filePath,
        GENERIC_READ,
        FILE_SHARE_READ,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL
    );
    
    if (hFile == INVALID_HANDLE_VALUE) {
        printf("[-] 无法打开文件: %s\n", filePath);
        return FALSE;
    }
    
    *size = GetFileSize(hFile, NULL);
    if (*size == 0 || *size == INVALID_FILE_SIZE) {
        CloseHandle(hFile);
        return FALSE;
    }
    
    *data = (LPBYTE)VirtualAlloc(
        NULL,
        *size,
        MEM_COMMIT | MEM_RESERVE,
        PAGE_READWRITE
    );
    
    if (!*data) {
        CloseHandle(hFile);
        return FALSE;
    }
    
    DWORD bytesRead;
    if (!ReadFile(hFile, *data, *size, &bytesRead, NULL) || bytesRead != *size) {
        VirtualFree(*data, 0, MEM_RELEASE);
        CloseHandle(hFile);
        return FALSE;
    }
    
    CloseHandle(hFile);
    return TRUE;
}

// 初始化PE信息
BOOL InitPEFileInfo(PPE_FILE_INFO info, LPBYTE fileData, DWORD fileSize) {
    ZeroMemory(info, sizeof(PE_FILE_INFO));
    
    info->fileData = fileData;
    info->fileSize = fileSize;
    
    // 验证DOS头
    info->dosHeader = (PIMAGE_DOS_HEADER)fileData;
    if (info->dosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
        printf("[-] 无效的DOS签名\n");
        return FALSE;
    }
    
    // 验证PE签名
    PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(
        fileData + info->dosHeader->e_lfanew
    );
    
    if (ntHeaders->Signature != IMAGE_NT_SIGNATURE) {
        printf("[-] 无效的PE签名\n");
        return FALSE;
    }
    
    info->fileHeader = &ntHeaders->FileHeader;
    info->sectionCount = info->fileHeader->NumberOfSections;
    info->machine = info->fileHeader->Machine;
    info->isDll = (info->fileHeader->Characteristics & IMAGE_FILE_DLL) != 0;
    
    // 判断位数
    info->is64Bit = (ntHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC);
    
    if (info->is64Bit) {
        info->ntHeaders64 = (PIMAGE_NT_HEADERS64)ntHeaders;
        PIMAGE_OPTIONAL_HEADER64 opt = &info->ntHeaders64->OptionalHeader;
        
        info->imageBase = opt->ImageBase;
        info->entryPoint = opt->AddressOfEntryPoint;
        info->sizeOfImage = opt->SizeOfImage;
        info->headerSize = opt->SizeOfHeaders;
        info->sectionAlignment = opt->SectionAlignment;
        info->fileAlignment = opt->FileAlignment;
        info->subsystem = opt->Subsystem;
        info->dllCharacteristics = opt->DllCharacteristics;
        info->dataDirectories = opt->DataDirectory;
        info->dataDirectoryCount = opt->NumberOfRvaAndSizes;
        
        info->sectionHeaders = IMAGE_FIRST_SECTION(info->ntHeaders64);
    } else {
        info->ntHeaders32 = (PIMAGE_NT_HEADERS32)ntHeaders;
        PIMAGE_OPTIONAL_HEADER32 opt = &info->ntHeaders32->OptionalHeader;
        
        info->imageBase = opt->ImageBase;
        info->entryPoint = opt->AddressOfEntryPoint;
        info->sizeOfImage = opt->SizeOfImage;
        info->headerSize = opt->SizeOfHeaders;
        info->sectionAlignment = opt->SectionAlignment;
        info->fileAlignment = opt->FileAlignment;
        info->subsystem = opt->Subsystem;
        info->dllCharacteristics = opt->DllCharacteristics;
        info->dataDirectories = opt->DataDirectory;
        info->dataDirectoryCount = opt->NumberOfRvaAndSizes;
        
        info->sectionHeaders = IMAGE_FIRST_SECTION(info->ntHeaders32);
    }
    
    // 收集区段信息
    for (WORD i = 0; i < info->sectionCount && i < 32; i++) {
        PIMAGE_SECTION_HEADER sec = &info->sectionHeaders[i];
        
        memcpy(info->sections[i].name, sec->Name, 8);
        info->sections[i].name[8] = '\0';
        info->sections[i].virtualAddress = sec->VirtualAddress;
        info->sections[i].virtualSize = sec->Misc.VirtualSize;
        info->sections[i].rawAddress = sec->PointerToRawData;
        info->sections[i].rawSize = sec->SizeOfRawData;
        info->sections[i].characteristics = sec->Characteristics;
    }
    
    return TRUE;
}

5.3、验证PE文件

// 验证PE是否可以加壳
BOOL ValidatePEForPacking(PPE_FILE_INFO info) {
    printf("[*] 验证PE文件...\n");
    
    // 检查是否已加壳
    for (WORD i = 0; i < info->sectionCount; i++) {
        // 检查常见壳的区段名
        if (strstr(info->sections[i].name, "UPX") ||
            strstr(info->sections[i].name, "ASP") ||
            strstr(info->sections[i].name, ".nsp") ||
            strstr(info->sections[i].name, ".packed")) {
            printf("[!] 警告: 检测到可能已加壳 (%s)\n", info->sections[i].name);
        }
    }
    
    // 检查是否是.NET程序
    if (info->dataDirectoryCount > 14 &&
        info->dataDirectories[14].VirtualAddress != 0) {
        printf("[-] 不支持.NET程序\n");
        return FALSE;
    }
    
    // 检查是否有重定位表(对于EXE很重要)
    if (info->dataDirectories[5].VirtualAddress == 0 && !info->isDll) {
        printf("[!] 警告: 没有重定位表,ASLR环境可能无法正常工作\n");
    }
    
    // 检查区段数量是否有空间添加新区段
    DWORD spaceForNewSection = info->headerSize - 
        ((LPBYTE)&info->sectionHeaders[info->sectionCount] - info->fileData);
    
    if (spaceForNewSection < sizeof(IMAGE_SECTION_HEADER)) {
        printf("[-] PE头没有足够空间添加新区段\n");
        return FALSE;
    }
    
    printf("[+] PE文件验证通过\n");
    return TRUE;
}

// 打印PE信息
void PrintPEInfo(PPE_FILE_INFO info) {
    printf("\n========== PE文件信息 ==========\n");
    printf("架构:         %s\n", info->is64Bit ? "x64" : "x86");
    printf("类型:         %s\n", info->isDll ? "DLL" : "EXE");
    printf("入口点:       0x%08X\n", info->entryPoint);
    printf("映像基址:     0x%llX\n", info->imageBase);
    printf("映像大小:     0x%08X\n", info->sizeOfImage);
    printf("区段对齐:     0x%08X\n", info->sectionAlignment);
    printf("文件对齐:     0x%08X\n", info->fileAlignment);
    printf("区段数量:     %d\n", info->sectionCount);
    
    printf("\n区段列表:\n");
    printf("%-8s %-10s %-10s %-10s %-10s\n", 
           "Name", "VirtAddr", "VirtSize", "RawAddr", "RawSize");
    
    for (WORD i = 0; i < info->sectionCount; i++) {
        printf("%-8s 0x%08X 0x%08X 0x%08X 0x%08X\n",
               info->sections[i].name,
               info->sections[i].virtualAddress,
               info->sections[i].virtualSize,
               info->sections[i].rawAddress,
               info->sections[i].rawSize);
    }
    
    printf("\n数据目录:\n");
    const char* dirNames[] = {
        "Export", "Import", "Resource", "Exception", "Security",
        "BaseReloc", "Debug", "Architecture", "GlobalPtr", "TLS",
        "LoadConfig", "BoundImport", "IAT", "DelayImport", "CLR"
    };
    
    for (DWORD i = 0; i < 15 && i < info->dataDirectoryCount; i++) {
        if (info->dataDirectories[i].VirtualAddress != 0) {
            printf("  [%d] %-12s RVA: 0x%08X  Size: 0x%08X\n",
                   i, dirNames[i],
                   info->dataDirectories[i].VirtualAddress,
                   info->dataDirectories[i].Size);
        }
    }
}

5.4、加壳器初始化

// 初始化加壳器
BOOL InitPacker(PPACKER_CONTEXT ctx, const char* targetPath, const char* stubPath) {
    ZeroMemory(ctx, sizeof(PACKER_CONTEXT));
    
    printf("[*] 初始化加壳器...\n");
    
    // 加载目标PE
    LPBYTE targetData;
    DWORD targetSize;
    
    if (!LoadFileToMemory(targetPath, &targetData, &targetSize)) {
        printf("[-] 加载目标文件失败\n");
        return FALSE;
    }
    
    if (!InitPEFileInfo(&ctx->targetPE, targetData, targetSize)) {
        printf("[-] 解析目标PE失败\n");
        VirtualFree(targetData, 0, MEM_RELEASE);
        return FALSE;
    }
    
    PrintPEInfo(&ctx->targetPE);
    
    if (!ValidatePEForPacking(&ctx->targetPE)) {
        VirtualFree(targetData, 0, MEM_RELEASE);
        return FALSE;
    }
    
    // 加载壳PE
    LPBYTE stubData;
    DWORD stubSize;
    
    if (!LoadFileToMemory(stubPath, &stubData, &stubSize)) {
        printf("[-] 加载壳文件失败\n");
        VirtualFree(targetData, 0, MEM_RELEASE);
        return FALSE;
    }
    
    if (!InitPEFileInfo(&ctx->stubPE, stubData, stubSize)) {
        printf("[-] 解析壳PE失败\n");
        VirtualFree(targetData, 0, MEM_RELEASE);
        VirtualFree(stubData, 0, MEM_RELEASE);
        return FALSE;
    }
    
    // 验证壳与目标架构一致
    if (ctx->targetPE.is64Bit != ctx->stubPE.is64Bit) {
        printf("[-] 壳和目标架构不匹配\n");
        VirtualFree(targetData, 0, MEM_RELEASE);
        VirtualFree(stubData, 0, MEM_RELEASE);
        return FALSE;
    }
    
    printf("[+] 加壳器初始化完成\n");
    
    return TRUE;
}

// 清理加壳器
void CleanupPacker(PPACKER_CONTEXT ctx) {
    if (ctx->targetPE.fileData) {
        VirtualFree(ctx->targetPE.fileData, 0, MEM_RELEASE);
    }
    if (ctx->stubPE.fileData) {
        VirtualFree(ctx->stubPE.fileData, 0, MEM_RELEASE);
    }
    if (ctx->stubCode) {
        VirtualFree(ctx->stubCode, 0, MEM_RELEASE);
    }
}

6、课后作业

  1. 完善PE解析功能

    • 添加更多验证检查
    • 处理各种异常PE
    • 支持64位PE
  2. 实现命令行接口

    • 解析命令行参数
    • 支持多种选项配置
    • 添加帮助信息
  3. 测试不同PE文件

    • 测试各种EXE和DLL
    • 测试已加壳文件的检测
    • 验证信息提取的准确性