加密壳
5、加壳工具初始化PE文件信息
1、课程目标
- 设计加壳工具的架构
- 实现PE文件的读取和解析
- 收集加壳所需的信息
- 验证目标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、课后作业
-
完善PE解析功能
- 添加更多验证检查
- 处理各种异常PE
- 支持64位PE
-
实现命令行接口
- 解析命令行参数
- 支持多种选项配置
- 添加帮助信息
-
测试不同PE文件
- 测试各种EXE和DLL
- 测试已加壳文件的检测
- 验证信息提取的准确性