加密壳

7、添加区段完成加壳流程

1、课程目标

  1. 实现向PE添加新区段
  2. 完成原始代码加密
  3. 嵌入壳代码和配置
  4. 生成完整的加壳PE

2、名词解释

名词全称解释
Section Addition区段添加向PE添加新区段
Code Encryption代码加密加密原始代码段
Config Embed配置嵌入将配置信息嵌入壳
PE RebuildPE重建生成新的PE文件

3、使用工具

  • Visual Studio 2022
  • CFF Explorer
  • PE-bear
  • x64dbg

4、技术原理

4.1、加壳PE构建流程

原始PE                          加壳后PE
+---------------+              +---------------+
| DOS Header    |              | DOS Header    |
| PE Header     |              | PE Header     | (修改)
| Section Hdrs  |              | Section Hdrs  | (增加)
+---------------+              +---------------+
| .text (代码)  |   加密       | .packed       |
| .data         | -------->    | (加密的原始   |
| .rdata        |              |  区段数据)    |
| .rsrc         |              +---------------+
+---------------+              | .shell        |
                               | (壳代码+配置) |
                               +---------------+
                               
修改项:
1. NumberOfSections + 2 (或合并为1个)
2. AddressOfEntryPoint -> 壳入口
3. SizeOfImage 更新
4. 添加新区段头

5、代码实现

5.1、加密原始代码

// 加密原始PE的代码段
BOOL EncryptTargetSections(PPACKER_CONTEXT ctx) {
    printf("\n===== 加密目标PE =====\n");
    
    PPE_FILE_INFO target = &ctx->targetPE;
    
    // 找到需要加密的区段
    for (WORD i = 0; i < target->sectionCount; i++) {
        PIMAGE_SECTION_HEADER sec = &target->sectionHeaders[i];
        
        // 加密代码段和数据段
        BOOL shouldEncrypt = FALSE;
        
        if (sec->Characteristics & IMAGE_SCN_CNT_CODE) {
            shouldEncrypt = TRUE;
        }
        if (sec->Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA) {
            // 可选:加密初始化数据
            // shouldEncrypt = TRUE;
        }
        
        // 跳过资源段(通常需要保留)
        if (memcmp(sec->Name, ".rsrc", 5) == 0) {
            shouldEncrypt = FALSE;
        }
        
        // 跳过重定位段
        if (memcmp(sec->Name, ".reloc", 6) == 0) {
            shouldEncrypt = FALSE;
        }
        
        if (shouldEncrypt && sec->SizeOfRawData > 0) {
            LPBYTE sectionData = target->fileData + sec->PointerToRawData;
            DWORD sectionSize = sec->SizeOfRawData;
            
            printf("[*] 加密区段: %-8s (0x%08X bytes)\n", 
                   sec->Name, sectionSize);
            
            // 应用加密
            switch (ctx->encryptionType) {
                case ENCRYPT_XOR:
                    XorCrypt(sectionData, sectionSize, 
                            (BYTE*)&ctx->encryptionKey, 4);
                    break;
                    
                case ENCRYPT_RC4: {
                    RC4_STATE rc4;
                    Rc4Init(&rc4, (BYTE*)&ctx->encryptionKey, 4);
                    Rc4Crypt(&rc4, sectionData, sectionSize);
                    break;
                }
                
                case ENCRYPT_TRANSFORM:
                    TransformXor(sectionData, sectionSize, ctx->encryptionKey);
                    break;
            }
        }
    }
    
    printf("[+] 加密完成\n");
    return TRUE;
}

5.2、添加新区段

// 添加壳区段
BOOL AddShellSection(PPACKER_CONTEXT ctx, LPBYTE* newPE, DWORD* newPESize) {
    printf("\n===== 添加壳区段 =====\n");
    
    PPE_FILE_INFO target = &ctx->targetPE;
    
    // 准备壳配置
    SHELL_CONFIG config = {0};
    config.magic = SHELL_MAGIC;
    config.originalOEP = target->entryPoint;
    config.encryptionType = ctx->encryptionType;
    config.key = ctx->encryptionKey;
    config.flags = FLAG_ANTI_DEBUG | FLAG_INTEGRITY;
    
    // 计算壳区段大小(代码 + 配置 + 填充)
    DWORD shellDataSize = ctx->stubCodeSize + sizeof(SHELL_CONFIG);
    DWORD alignedShellSize = (shellDataSize + target->fileAlignment - 1) & 
                             ~(target->fileAlignment - 1);
    
    // 计算新区段位置
    PIMAGE_SECTION_HEADER lastSection = 
        &target->sectionHeaders[target->sectionCount - 1];
    
    // 新区段的文件偏移
    DWORD newSectionFOA = lastSection->PointerToRawData + lastSection->SizeOfRawData;
    newSectionFOA = (newSectionFOA + target->fileAlignment - 1) & 
                    ~(target->fileAlignment - 1);
    
    // 新区段的RVA
    DWORD newSectionRVA = lastSection->VirtualAddress + lastSection->Misc.VirtualSize;
    newSectionRVA = (newSectionRVA + target->sectionAlignment - 1) & 
                    ~(target->sectionAlignment - 1);
    
    // 更新配置中的打包数据信息
    // 这里假设整个原始.text段被加密
    for (WORD i = 0; i < target->sectionCount; i++) {
        if (target->sectionHeaders[i].Characteristics & IMAGE_SCN_CNT_CODE) {
            config.packedDataRVA = target->sectionHeaders[i].VirtualAddress;
            config.packedDataSize = target->sectionHeaders[i].SizeOfRawData;
            break;
        }
    }
    
    printf("[*] 新区段位置:\n");
    printf("    FOA: 0x%08X\n", newSectionFOA);
    printf("    RVA: 0x%08X\n", newSectionRVA);
    printf("    Size: 0x%08X\n", alignedShellSize);
    
    // 计算新PE大小
    *newPESize = newSectionFOA + alignedShellSize;
    
    // 分配新PE内存
    *newPE = (LPBYTE)VirtualAlloc(
        NULL, *newPESize,
        MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE
    );
    
    if (!*newPE) {
        printf("[-] 分配内存失败\n");
        return FALSE;
    }
    
    // 复制原始PE
    memcpy(*newPE, target->fileData, target->fileSize);
    
    // 填充到新大小
    ZeroMemory(*newPE + target->fileSize, *newPESize - target->fileSize);
    
    // 更新PE头
    PIMAGE_DOS_HEADER newDos = (PIMAGE_DOS_HEADER)*newPE;
    PIMAGE_NT_HEADERS newNt = (PIMAGE_NT_HEADERS)(*newPE + newDos->e_lfanew);
    
    // 增加区段数量
    newNt->FileHeader.NumberOfSections++;
    
    // 添加新区段头
    PIMAGE_SECTION_HEADER newSecHeader = IMAGE_FIRST_SECTION(newNt) + 
                                         target->sectionCount;
    
    ZeroMemory(newSecHeader, sizeof(IMAGE_SECTION_HEADER));
    memcpy(newSecHeader->Name, ".shell", 7);
    newSecHeader->Misc.VirtualSize = shellDataSize;
    newSecHeader->VirtualAddress = newSectionRVA;
    newSecHeader->SizeOfRawData = alignedShellSize;
    newSecHeader->PointerToRawData = newSectionFOA;
    newSecHeader->Characteristics = IMAGE_SCN_CNT_CODE | 
                                    IMAGE_SCN_MEM_EXECUTE | 
                                    IMAGE_SCN_MEM_READ;
    
    // 更新SizeOfImage
    DWORD newImageSize = newSectionRVA + shellDataSize;
    newImageSize = (newImageSize + target->sectionAlignment - 1) & 
                   ~(target->sectionAlignment - 1);
    
    if (target->is64Bit) {
        PIMAGE_OPTIONAL_HEADER64 opt = (PIMAGE_OPTIONAL_HEADER64)&newNt->OptionalHeader;
        opt->SizeOfImage = newImageSize;
        
        // 修改入口点到壳
        opt->AddressOfEntryPoint = newSectionRVA;  // 壳代码开始处
    } else {
        PIMAGE_OPTIONAL_HEADER32 opt = (PIMAGE_OPTIONAL_HEADER32)&newNt->OptionalHeader;
        opt->SizeOfImage = newImageSize;
        opt->AddressOfEntryPoint = newSectionRVA;
    }
    
    // 复制壳代码
    memcpy(*newPE + newSectionFOA, ctx->stubCode, ctx->stubCodeSize);
    
    // 复制配置(放在壳代码末尾)
    memcpy(*newPE + newSectionFOA + ctx->stubCodeSize, &config, sizeof(config));
    
    printf("[+] 壳区段添加完成\n");
    printf("    新入口点: 0x%08X\n", newSectionRVA);
    printf("    新映像大小: 0x%08X\n", newImageSize);
    
    return TRUE;
}

5.3、完整加壳流程

// 执行加壳
BOOL PackExecutable(PPACKER_CONTEXT ctx) {
    printf("\n========== 开始加壳 ==========\n");
    
    // 1. 准备壳代码
    if (!PrepareStubCode(ctx)) {
        printf("[-] 准备壳代码失败\n");
        return FALSE;
    }
    
    // 2. 加密目标PE
    if (!EncryptTargetSections(ctx)) {
        printf("[-] 加密失败\n");
        return FALSE;
    }
    
    // 3. 构建新PE
    LPBYTE packedPE;
    DWORD packedSize;
    
    if (!AddShellSection(ctx, &packedPE, &packedSize)) {
        printf("[-] 添加壳区段失败\n");
        return FALSE;
    }
    
    // 4. 保存输出文件
    if (!SaveFile(ctx->outputPath, packedPE, packedSize)) {
        printf("[-] 保存文件失败\n");
        VirtualFree(packedPE, 0, MEM_RELEASE);
        return FALSE;
    }
    
    printf("\n========== 加壳完成 ==========\n");
    printf("输出文件: %s\n", ctx->outputPath);
    printf("原始大小: %d bytes\n", ctx->targetPE.fileSize);
    printf("加壳大小: %d bytes\n", packedSize);
    printf("增加: %d bytes (%.1f%%)\n", 
           packedSize - ctx->targetPE.fileSize,
           (float)(packedSize - ctx->targetPE.fileSize) / ctx->targetPE.fileSize * 100);
    
    VirtualFree(packedPE, 0, MEM_RELEASE);
    
    return TRUE;
}

// 保存文件
BOOL SaveFile(const char* path, LPBYTE data, DWORD size) {
    HANDLE hFile = CreateFileA(
        path,
        GENERIC_WRITE,
        0,
        NULL,
        CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,
        NULL
    );
    
    if (hFile == INVALID_HANDLE_VALUE) {
        return FALSE;
    }
    
    DWORD written;
    BOOL result = WriteFile(hFile, data, size, &written, NULL);
    
    CloseHandle(hFile);
    
    return result && written == size;
}

5.4、加壳器主程序

// 使用示例
int main(int argc, char* argv[]) {
    printf("========================================\n");
    printf("         Simple PE Packer v1.0         \n");
    printf("========================================\n\n");
    
    if (argc < 4) {
        printf("用法: %s <目标PE> <壳文件> <输出文件> [密钥]\n", argv[0]);
        printf("\n示例: packer.exe target.exe stub.exe packed.exe 12345678\n");
        return 1;
    }
    
    PACKER_CONTEXT ctx;
    
    // 初始化
    if (!InitPacker(&ctx, argv[1], argv[2])) {
        printf("[-] 初始化失败\n");
        return 1;
    }
    
    // 设置输出路径
    strcpy(ctx.outputPath, argv[3]);
    
    // 设置加密参数
    ctx.encryptionType = ENCRYPT_XOR;
    ctx.encryptionKey = (argc > 4) ? strtoul(argv[4], NULL, 16) : 0x12345678;
    
    printf("[*] 加密类型: XOR\n");
    printf("[*] 加密密钥: 0x%08X\n", ctx.encryptionKey);
    
    // 执行加壳
    BOOL success = PackExecutable(&ctx);
    
    // 清理
    CleanupPacker(&ctx);
    
    return success ? 0 : 1;
}

6、课后作业

  1. 实现完整的加壳工具

    • 整合所有功能
    • 添加命令行参数
    • 支持多种加密选项
  2. 测试加壳效果

    • 测试各种PE文件
    • 验证加壳后能正常运行
    • 测试免杀效果
  3. 优化加壳工具

    • 压缩原始代码
    • 添加更多混淆
    • 减小增加的体积