加密壳

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编写壳
    • 减少重定位项
    • 减小壳代码体积