加密壳
6、获取壳代码段数据并修复重定位
1、课程目标
- 从壳PE中提取代码段
- 理解壳代码的重定位需求
- 实现壳代码的重定位修复
- 准备壳代码嵌入目标PE
2、名词解释
| 名词 | 全称 | 解释 |
|---|---|---|
| Stub Extraction | 壳提取 | 从壳PE中提取代码 |
| Relocation Fix | 重定位修复 | 调整壳中的地址引用 |
| Delta | 差值 | 新旧地址的差值 |
| Base Relocation | 基址重定位 | PE重定位表中的项 |
3、使用工具
- Visual Studio 2022
- x64dbg
- PE-bear
4、技术原理
4.1、壳代码提取流程
壳PE文件
|
v
+-------------------+
| 定位.text区段 |
+-------------------+
|
v
+-------------------+
| 复制代码数据 |
+-------------------+
|
v
+-------------------+
| 解析重定位表 |
+-------------------+
|
v
+-------------------+
| 收集重定位项 |
+-------------------+
|
v
+-------------------+
| 计算新的Delta |
| (目标位置 - 原位置)|
+-------------------+
|
v
+-------------------+
| 应用重定位修复 |
+-------------------+
|
v
修复后的壳代码
5、代码实现
5.1、提取壳代码
// 壳代码信息
typedef struct _STUB_CODE_INFO {
LPBYTE code; // 代码数据
DWORD codeSize; // 代码大小
DWORD entryOffset; // 入口点偏移(相对于代码开始)
// 重定位信息
struct {
DWORD offset; // 代码内偏移
WORD type; // 重定位类型
} relocations[4096];
DWORD relocCount;
// 原始信息
DWORD originalRVA; // 原始区段RVA
ULONGLONG originalImageBase;
} STUB_CODE_INFO, *PSTUB_CODE_INFO;
// 提取壳代码段
BOOL ExtractStubCode(PPE_FILE_INFO stubPE, PSTUB_CODE_INFO stubInfo) {
ZeroMemory(stubInfo, sizeof(STUB_CODE_INFO));
printf("[*] 提取壳代码...\n");
// 找到.text区段
PIMAGE_SECTION_HEADER textSection = NULL;
for (WORD i = 0; i < stubPE->sectionCount; i++) {
if (memcmp(stubPE->sectionHeaders[i].Name, ".text", 5) == 0) {
textSection = &stubPE->sectionHeaders[i];
break;
}
}
if (!textSection) {
printf("[-] 壳中没有.text区段\n");
return FALSE;
}
// 复制代码
stubInfo->codeSize = textSection->SizeOfRawData;
stubInfo->code = (LPBYTE)VirtualAlloc(
NULL, stubInfo->codeSize,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE
);
if (!stubInfo->code) {
return FALSE;
}
memcpy(stubInfo->code,
stubPE->fileData + textSection->PointerToRawData,
stubInfo->codeSize);
stubInfo->originalRVA = textSection->VirtualAddress;
stubInfo->originalImageBase = stubPE->imageBase;
// 计算入口点偏移
if (stubPE->entryPoint >= textSection->VirtualAddress &&
stubPE->entryPoint < textSection->VirtualAddress + textSection->Misc.VirtualSize) {
stubInfo->entryOffset = stubPE->entryPoint - textSection->VirtualAddress;
} else {
printf("[!] 入口点不在.text区段内\n");
stubInfo->entryOffset = 0;
}
printf("[+] 提取壳代码: %d bytes\n", stubInfo->codeSize);
printf(" 原始RVA: 0x%08X\n", stubInfo->originalRVA);
printf(" 入口偏移: 0x%08X\n", stubInfo->entryOffset);
return TRUE;
}
5.2、收集重定位信息
// 从壳PE收集重定位信息
BOOL CollectStubRelocations(PPE_FILE_INFO stubPE, PSTUB_CODE_INFO stubInfo) {
printf("[*] 收集壳重定位信息...\n");
// 检查是否有重定位表
if (stubPE->dataDirectoryCount <= 5 ||
stubPE->dataDirectories[5].VirtualAddress == 0) {
printf("[!] 壳没有重定位表\n");
return TRUE; // 不是错误,可能是PIC代码
}
DWORD relocRVA = stubPE->dataDirectories[5].VirtualAddress;
DWORD relocSize = stubPE->dataDirectories[5].Size;
// 找到重定位数据
DWORD relocFOA = RvaToFoa(stubPE, relocRVA);
PIMAGE_BASE_RELOCATION relocBlock = (PIMAGE_BASE_RELOCATION)(
stubPE->fileData + relocFOA
);
// .text区段范围
DWORD textStart = stubInfo->originalRVA;
DWORD textEnd = textStart + stubInfo->codeSize;
stubInfo->relocCount = 0;
// 遍历重定位块
while ((LPBYTE)relocBlock < stubPE->fileData + relocFOA + relocSize &&
relocBlock->VirtualAddress != 0) {
// 检查此块是否与.text区段相关
if (relocBlock->VirtualAddress >= textStart &&
relocBlock->VirtualAddress < textEnd) {
DWORD numEntries = (relocBlock->SizeOfBlock - 8) / 2;
WORD* entries = (WORD*)(relocBlock + 1);
for (DWORD i = 0; i < numEntries; i++) {
WORD type = entries[i] >> 12;
WORD offset = entries[i] & 0x0FFF;
if (type == IMAGE_REL_BASED_ABSOLUTE) {
continue; // 填充项,跳过
}
// 计算在代码中的偏移
DWORD codeOffset = relocBlock->VirtualAddress - textStart + offset;
if (codeOffset < stubInfo->codeSize) {
stubInfo->relocations[stubInfo->relocCount].offset = codeOffset;
stubInfo->relocations[stubInfo->relocCount].type = type;
stubInfo->relocCount++;
if (stubInfo->relocCount >= 4096) {
printf("[!] 重定位项过多\n");
break;
}
}
}
}
// 下一个块
relocBlock = (PIMAGE_BASE_RELOCATION)(
(LPBYTE)relocBlock + relocBlock->SizeOfBlock
);
}
printf("[+] 收集到 %d 个重定位项\n", stubInfo->relocCount);
return TRUE;
}
// RVA转FOA辅助函数
DWORD RvaToFoa(PPE_FILE_INFO pe, DWORD rva) {
for (WORD i = 0; i < pe->sectionCount; i++) {
DWORD start = pe->sectionHeaders[i].VirtualAddress;
DWORD end = start + pe->sectionHeaders[i].Misc.VirtualSize;
if (rva >= start && rva < end) {
return pe->sectionHeaders[i].PointerToRawData + (rva - start);
}
}
return rva; // 头部区域
}
5.3、应用重定位修复
// 修复壳代码的重定位
BOOL FixStubRelocations(PSTUB_CODE_INFO stubInfo,
ULONGLONG newImageBase,
DWORD newSectionRVA) {
printf("[*] 修复壳重定位...\n");
// 计算Delta
LONGLONG delta = 0;
// Delta = (新基址 + 新RVA) - (旧基址 + 旧RVA)
delta = ((LONGLONG)newImageBase + newSectionRVA) -
((LONGLONG)stubInfo->originalImageBase + stubInfo->originalRVA);
printf(" 原始位置: 0x%llX + 0x%08X\n",
stubInfo->originalImageBase, stubInfo->originalRVA);
printf(" 新位置: 0x%llX + 0x%08X\n", newImageBase, newSectionRVA);
printf(" Delta: 0x%llX\n", delta);
if (delta == 0) {
printf("[+] 不需要重定位修复\n");
return TRUE;
}
// 应用重定位
int fixCount = 0;
for (DWORD i = 0; i < stubInfo->relocCount; i++) {
DWORD offset = stubInfo->relocations[i].offset;
WORD type = stubInfo->relocations[i].type;
if (offset >= stubInfo->codeSize) {
continue;
}
switch (type) {
case IMAGE_REL_BASED_HIGHLOW: {
// 32位重定位
DWORD* ptr = (DWORD*)(stubInfo->code + offset);
*ptr += (DWORD)delta;
fixCount++;
break;
}
case IMAGE_REL_BASED_DIR64: {
// 64位重定位
ULONGLONG* ptr = (ULONGLONG*)(stubInfo->code + offset);
*ptr += delta;
fixCount++;
break;
}
case IMAGE_REL_BASED_HIGH: {
WORD* ptr = (WORD*)(stubInfo->code + offset);
*ptr += HIWORD((DWORD)delta);
fixCount++;
break;
}
case IMAGE_REL_BASED_LOW: {
WORD* ptr = (WORD*)(stubInfo->code + offset);
*ptr += LOWORD((DWORD)delta);
fixCount++;
break;
}
default:
printf("[!] 未知重定位类型: %d\n", type);
break;
}
}
printf("[+] 修复了 %d 处重定位\n", fixCount);
return TRUE;
}
5.4、完整的壳准备流程
// 准备壳代码
BOOL PrepareStubCode(PPACKER_CONTEXT ctx) {
printf("\n===== 准备壳代码 =====\n");
// 1. 提取壳代码
STUB_CODE_INFO stubInfo;
if (!ExtractStubCode(&ctx->stubPE, &stubInfo)) {
return FALSE;
}
// 2. 收集重定位信息
if (!CollectStubRelocations(&ctx->stubPE, &stubInfo)) {
VirtualFree(stubInfo.code, 0, MEM_RELEASE);
return FALSE;
}
// 3. 计算新区段位置
// 新区段将添加到目标PE的最后
PIMAGE_SECTION_HEADER lastSection =
&ctx->targetPE.sectionHeaders[ctx->targetPE.sectionCount - 1];
DWORD newSectionRVA = lastSection->VirtualAddress + lastSection->Misc.VirtualSize;
// 对齐到SectionAlignment
newSectionRVA = (newSectionRVA + ctx->targetPE.sectionAlignment - 1) &
~(ctx->targetPE.sectionAlignment - 1);
// 4. 修复重定位
if (!FixStubRelocations(&stubInfo, ctx->targetPE.imageBase, newSectionRVA)) {
VirtualFree(stubInfo.code, 0, MEM_RELEASE);
return FALSE;
}
// 5. 保存壳代码信息
ctx->stubCode = stubInfo.code;
ctx->stubCodeSize = stubInfo.codeSize;
printf("[+] 壳代码准备完成\n");
return TRUE;
}
// 验证壳代码是否为PIC
BOOL IsStubPIC(PSTUB_CODE_INFO stubInfo) {
// 检查是否有重定位项
// PIC代码不应该有需要修复的绝对地址
return (stubInfo->relocCount == 0);
}
// 分析壳代码
void AnalyzeStubCode(PSTUB_CODE_INFO stubInfo) {
printf("\n===== 壳代码分析 =====\n");
printf("代码大小: %d bytes\n", stubInfo->codeSize);
printf("入口偏移: 0x%08X\n", stubInfo->entryOffset);
printf("重定位项数: %d\n", stubInfo->relocCount);
printf("是否PIC: %s\n", IsStubPIC(stubInfo) ? "是" : "否");
// 显示前几个字节
printf("代码头部: ");
for (DWORD i = 0; i < 16 && i < stubInfo->codeSize; i++) {
printf("%02X ", stubInfo->code[i]);
}
printf("\n");
// 显示入口点代码
if (stubInfo->entryOffset < stubInfo->codeSize) {
printf("入口代码: ");
for (DWORD i = 0; i < 16 && stubInfo->entryOffset + i < stubInfo->codeSize; i++) {
printf("%02X ", stubInfo->code[stubInfo->entryOffset + i]);
}
printf("\n");
}
}
6、课后作业
-
实现完整的壳提取
- 支持提取多个区段
- 处理特殊区段名
- 验证提取的代码
-
测试重定位修复
- 使用不同的目标地址测试
- 验证修复后代码的正确性
- 处理边界情况
-
优化壳代码
- 尽量使用PIC编写壳
- 减少重定位项
- 减小壳代码体积