加密壳

6、获取壳代码段数据并修复重定位

1、课程目标

  1. 从壳PE中提取代码段
  2. 理解壳代码的重定位需求
  3. 实现壳代码的重定位修复
  4. 准备壳代码嵌入目标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、课后作业

  1. 实现完整的壳提取

    • 支持提取多个区段
    • 处理特殊区段名
    • 验证提取的代码
  2. 测试重定位修复

    • 使用不同的目标地址测试
    • 验证修复后代码的正确性
    • 处理边界情况
  3. 优化壳代码

    • 尽量使用PIC编写壳
    • 减少重定位项
    • 减小壳代码体积