加密壳
8、阶段合集
1、课程目标
- 完成完整的加壳/脱壳系统
- 实现壳代码的完整功能
- 添加高级特性和优化
- 测试和验证加壳效果
2、名词解释
| 名词 | 全称 | 解释 |
|---|---|---|
| Complete Packer | 完整加壳器 | 包含所有功能的加壳工具 |
| Unpacker | 脱壳器 | 还原加壳程序的工具 |
| Memory Dump | 内存转储 | 从内存提取已解密的代码 |
| IAT Rebuild | 导入表重建 | 恢复被破坏的导入表 |
3、使用工具
- Visual Studio 2022
- x64dbg
- Scylla(IAT重建)
- PE-bear
4、技术原理
4.1、完整加壳系统架构
+------------------+ +------------------+ +------------------+
| 加壳器 | | 壳代码 | | 脱壳器 |
| (Packer) | | (Stub) | | (Unpacker) |
+------------------+ +------------------+ +------------------+
| | |
v v v
+------------------+ +------------------+ +------------------+
| - 解析目标PE | | - 获取API | | - 运行程序 |
| - 加密代码段 | | - 反调试检测 | | - 等待OEP |
| - 提取壳代码 | | - 解密代码 | | - 内存转储 |
| - 修复重定位 | | - 恢复导入表 | | - 重建导入表 |
| - 添加壳区段 | | - 跳转OEP | | - 修复PE头 |
| - 修改入口点 | +------------------+ | - 保存文件 |
| - 保存新PE | +------------------+
+------------------+
5、代码实现
5.1、完整的壳代码
// stub.c - 完整的壳代码实现
// 编译选项:/GS- /NODEFAULTLIB /ENTRY:ShellEntry
#include <windows.h>
// 哈希定义
#define HASH_KERNEL32 0x6A4ABC5B
#define HASH_GETPROCADDRESS 0x7C0DFCAA
#define HASH_LOADLIBRARYA 0xEC0E4E8E
#define HASH_VIRTUALPROTECT 0x7946C61B
// 壳配置
#pragma pack(push, 1)
typedef struct _SHELL_CONFIG {
DWORD magic;
DWORD originalOEP;
DWORD packedDataRVA;
DWORD packedDataSize;
DWORD originalDataSize;
DWORD encryptionType;
DWORD key;
DWORD shellCrc;
DWORD dataCrc;
DWORD flags;
} SHELL_CONFIG;
#pragma pack(pop)
#define SHELL_MAGIC 0xDEADC0DE
#define ENCRYPT_XOR 1
#define FLAG_ANTI_DEBUG 0x0001
// ROR13哈希
DWORD Ror13Hash(const char* str) {
DWORD hash = 0;
while (*str) {
hash = ((hash >> 13) | (hash << 19));
hash += *str++;
}
return hash;
}
// 通过哈希获取模块
HMODULE GetModuleByHash(DWORD hash) {
#ifdef _WIN64
PPEB peb = (PPEB)__readgsqword(0x60);
#else
PPEB peb;
__asm {
mov eax, fs:[0x30]
mov peb, eax
}
#endif
PPEB_LDR_DATA ldr = peb->Ldr;
PLIST_ENTRY head = &ldr->InMemoryOrderModuleList;
PLIST_ENTRY entry = head->Flink;
while (entry != head) {
PLDR_DATA_TABLE_ENTRY module = CONTAINING_RECORD(
entry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
if (module->BaseDllName.Buffer) {
DWORD modHash = 0;
WCHAR* name = module->BaseDllName.Buffer;
while (*name) {
WCHAR c = *name;
if (c >= L'A' && c <= L'Z') c += 0x20;
modHash = ((modHash >> 13) | (modHash << 19));
modHash += (char)c;
name++;
}
if (modHash == hash) {
return (HMODULE)module->DllBase;
}
}
entry = entry->Flink;
}
return NULL;
}
// 通过哈希获取函数
FARPROC GetFunctionByHash(HMODULE hModule, DWORD funcHash) {
LPBYTE base = (LPBYTE)hModule;
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)base;
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(base + dos->e_lfanew);
DWORD exportRVA = nt->OptionalHeader.DataDirectory[0].VirtualAddress;
if (exportRVA == 0) return NULL;
PIMAGE_EXPORT_DIRECTORY exp = (PIMAGE_EXPORT_DIRECTORY)(base + exportRVA);
DWORD* eat = (DWORD*)(base + exp->AddressOfFunctions);
DWORD* ent = (DWORD*)(base + exp->AddressOfNames);
WORD* eot = (WORD*)(base + exp->AddressOfNameOrdinals);
for (DWORD i = 0; i < exp->NumberOfNames; i++) {
char* name = (char*)(base + ent[i]);
if (Ror13Hash(name) == funcHash) {
return (FARPROC)(base + eat[eot[i]]);
}
}
return NULL;
}
// 检测调试器
BOOL IsBeingDebugged() {
#ifdef _WIN64
return *(PBYTE)(__readgsqword(0x60) + 2);
#else
BOOL result;
__asm {
mov eax, fs:[0x30]
movzx eax, byte ptr [eax + 2]
mov result, eax
}
return result;
#endif
}
// XOR解密
void XorDecrypt(LPBYTE data, DWORD size, DWORD key) {
LPBYTE keyBytes = (LPBYTE)&key;
for (DWORD i = 0; i < size; i++) {
data[i] ^= keyBytes[i % 4];
}
}
// 获取当前模块基址
LPBYTE GetCurrentImageBase() {
#ifdef _WIN64
PPEB peb = (PPEB)__readgsqword(0x60);
return *(LPBYTE*)(((LPBYTE)peb) + 0x10);
#else
LPBYTE base;
__asm {
mov eax, fs:[0x30]
mov eax, [eax + 0x08]
mov base, eax
}
return base;
#endif
}
// 定位壳配置
SHELL_CONFIG* FindShellConfig(LPBYTE imageBase) {
// 配置在壳代码末尾,通过搜索魔数定位
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)imageBase;
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(imageBase + dos->e_lfanew);
// 找到.shell区段
PIMAGE_SECTION_HEADER sections = IMAGE_FIRST_SECTION(nt);
for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++) {
if (sections[i].Name[0] == '.' && sections[i].Name[1] == 's') {
LPBYTE sectionEnd = imageBase + sections[i].VirtualAddress +
sections[i].Misc.VirtualSize;
// 配置在区段末尾
SHELL_CONFIG* config = (SHELL_CONFIG*)(sectionEnd - sizeof(SHELL_CONFIG));
if (config->magic == SHELL_MAGIC) {
return config;
}
}
}
return NULL;
}
// 壳入口
void __stdcall ShellEntry() {
// 获取kernel32
HMODULE hKernel32 = GetModuleByHash(HASH_KERNEL32);
if (!hKernel32) return;
// 获取API
typedef BOOL (WINAPI* PFN_VirtualProtect)(LPVOID, SIZE_T, DWORD, PDWORD);
PFN_VirtualProtect pVirtualProtect =
(PFN_VirtualProtect)GetFunctionByHash(hKernel32, HASH_VIRTUALPROTECT);
if (!pVirtualProtect) return;
// 获取基址
LPBYTE imageBase = GetCurrentImageBase();
// 定位配置
SHELL_CONFIG* config = FindShellConfig(imageBase);
if (!config || config->magic != SHELL_MAGIC) return;
// 反调试
if (config->flags & FLAG_ANTI_DEBUG) {
if (IsBeingDebugged()) {
return;
}
}
// 修改内存保护
LPBYTE packedData = imageBase + config->packedDataRVA;
DWORD oldProtect;
pVirtualProtect(packedData, config->packedDataSize,
PAGE_EXECUTE_READWRITE, &oldProtect);
// 解密
if (config->encryptionType == ENCRYPT_XOR) {
XorDecrypt(packedData, config->packedDataSize, config->key);
}
// 恢复保护
pVirtualProtect(packedData, config->packedDataSize,
PAGE_EXECUTE_READ, &oldProtect);
// 跳转到OEP
LPBYTE oep = imageBase + config->originalOEP;
((void(*)())oep)();
}
5.2、简单脱壳器
// unpacker.c - 简单脱壳器
#include <windows.h>
#include <stdio.h>
#include <tlhelp32.h>
// 等待程序到达OEP
BOOL WaitForOEP(HANDLE hProcess, LPVOID oepAddress) {
BYTE breakpoint = 0xCC;
BYTE original;
SIZE_T bytesRead, bytesWritten;
// 读取OEP原始字节
ReadProcessMemory(hProcess, oepAddress, &original, 1, &bytesRead);
// 设置断点
WriteProcessMemory(hProcess, oepAddress, &breakpoint, 1, &bytesWritten);
FlushInstructionCache(hProcess, oepAddress, 1);
// 继续执行
DEBUG_EVENT debugEvent;
while (1) {
if (!WaitForDebugEvent(&debugEvent, 10000)) {
printf("[-] 等待超时\n");
break;
}
if (debugEvent.dwDebugEventCode == EXCEPTION_DEBUG_EVENT) {
if (debugEvent.u.Exception.ExceptionRecord.ExceptionCode ==
EXCEPTION_BREAKPOINT) {
LPVOID exAddr = debugEvent.u.Exception.ExceptionRecord.ExceptionAddress;
if (exAddr == oepAddress) {
printf("[+] 到达OEP: 0x%p\n", oepAddress);
// 恢复原始字节
WriteProcessMemory(hProcess, oepAddress, &original, 1, &bytesWritten);
return TRUE;
}
}
}
ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId,
DBG_CONTINUE);
}
return FALSE;
}
// 转储内存
BOOL DumpProcess(HANDLE hProcess, LPVOID imageBase, const char* outputPath) {
printf("[*] 转储进程内存...\n");
// 读取DOS头
IMAGE_DOS_HEADER dosHeader;
ReadProcessMemory(hProcess, imageBase, &dosHeader, sizeof(dosHeader), NULL);
// 读取NT头
IMAGE_NT_HEADERS ntHeaders;
ReadProcessMemory(hProcess, (LPBYTE)imageBase + dosHeader.e_lfanew,
&ntHeaders, sizeof(ntHeaders), NULL);
DWORD imageSize = ntHeaders.OptionalHeader.SizeOfImage;
// 分配缓冲区
LPBYTE buffer = (LPBYTE)VirtualAlloc(NULL, imageSize,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
// 读取整个映像
ReadProcessMemory(hProcess, imageBase, buffer, imageSize, NULL);
// 修复区段(内存对齐 -> 文件对齐)
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(buffer + pDos->e_lfanew);
PIMAGE_SECTION_HEADER sections = IMAGE_FIRST_SECTION(pNt);
DWORD fileAlignment = pNt->OptionalHeader.FileAlignment;
// 计算新文件大小
DWORD newFileSize = pNt->OptionalHeader.SizeOfHeaders;
for (WORD i = 0; i < pNt->FileHeader.NumberOfSections; i++) {
sections[i].PointerToRawData = newFileSize;
sections[i].SizeOfRawData = (sections[i].Misc.VirtualSize + fileAlignment - 1) &
~(fileAlignment - 1);
newFileSize += sections[i].SizeOfRawData;
}
// 分配新缓冲区
LPBYTE fileBuffer = (LPBYTE)VirtualAlloc(NULL, newFileSize,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
// 复制头部
memcpy(fileBuffer, buffer, pNt->OptionalHeader.SizeOfHeaders);
// 复制区段
PIMAGE_NT_HEADERS pNewNt = (PIMAGE_NT_HEADERS)(fileBuffer + pDos->e_lfanew);
PIMAGE_SECTION_HEADER newSections = IMAGE_FIRST_SECTION(pNewNt);
for (WORD i = 0; i < pNewNt->FileHeader.NumberOfSections; i++) {
memcpy(fileBuffer + newSections[i].PointerToRawData,
buffer + newSections[i].VirtualAddress,
newSections[i].Misc.VirtualSize);
}
// 保存文件
HANDLE hFile = CreateFileA(outputPath, GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
DWORD written;
WriteFile(hFile, fileBuffer, newFileSize, &written, NULL);
CloseHandle(hFile);
VirtualFree(buffer, 0, MEM_RELEASE);
VirtualFree(fileBuffer, 0, MEM_RELEASE);
printf("[+] 转储完成: %s (%d bytes)\n", outputPath, newFileSize);
return TRUE;
}
// 脱壳主函数
BOOL Unpack(const char* packedPath, const char* outputPath, DWORD oepRVA) {
printf("[*] 开始脱壳: %s\n", packedPath);
// 创建调试进程
STARTUPINFOA si = { sizeof(si) };
PROCESS_INFORMATION pi;
if (!CreateProcessA(packedPath, NULL, NULL, NULL, FALSE,
DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS,
NULL, NULL, &si, &pi)) {
printf("[-] 创建进程失败\n");
return FALSE;
}
printf("[+] 进程创建: PID=%d\n", pi.dwProcessId);
// 获取基址
DEBUG_EVENT debugEvent;
LPVOID imageBase = NULL;
while (1) {
WaitForDebugEvent(&debugEvent, INFINITE);
if (debugEvent.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT) {
imageBase = debugEvent.u.CreateProcessInfo.lpBaseOfImage;
printf("[+] 映像基址: 0x%p\n", imageBase);
break;
}
ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId,
DBG_CONTINUE);
}
// 计算OEP地址
LPVOID oepAddress = (LPBYTE)imageBase + oepRVA;
printf("[*] OEP地址: 0x%p (RVA: 0x%08X)\n", oepAddress, oepRVA);
// 等待到达OEP
ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE);
if (WaitForOEP(pi.hProcess, oepAddress)) {
// 转储内存
DumpProcess(pi.hProcess, imageBase, outputPath);
}
// 终止进程
TerminateProcess(pi.hProcess, 0);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return TRUE;
}
int main(int argc, char* argv[]) {
if (argc < 4) {
printf("用法: %s <加壳程序> <输出文件> <OEP_RVA>\n", argv[0]);
printf("示例: unpacker.exe packed.exe dumped.exe 1000\n");
return 1;
}
DWORD oepRVA = strtoul(argv[3], NULL, 16);
Unpack(argv[1], argv[2], oepRVA);
return 0;
}
5.3、测试和验证
// 验证加壳效果
void TestPackedFile(const char* originalPath, const char* packedPath) {
printf("\n===== 加壳效果测试 =====\n");
// 比较文件大小
WIN32_FILE_ATTRIBUTE_DATA origAttr, packAttr;
GetFileAttributesExA(originalPath, GetFileExInfoStandard, &origAttr);
GetFileAttributesExA(packedPath, GetFileExInfoStandard, &packAttr);
printf("原始文件大小: %d bytes\n", origAttr.nFileSizeLow);
printf("加壳文件大小: %d bytes\n", packAttr.nFileSizeLow);
printf("体积变化: %+d bytes\n",
(int)packAttr.nFileSizeLow - (int)origAttr.nFileSizeLow);
// 运行测试
printf("\n[*] 运行加壳程序测试...\n");
STARTUPINFOA si = { sizeof(si) };
PROCESS_INFORMATION pi;
if (CreateProcessA(packedPath, NULL, NULL, NULL, FALSE, 0,
NULL, NULL, &si, &pi)) {
DWORD exitCode;
WaitForSingleObject(pi.hProcess, 5000);
GetExitCodeProcess(pi.hProcess, &exitCode);
if (exitCode == STILL_ACTIVE) {
printf("[+] 程序正常运行中\n");
TerminateProcess(pi.hProcess, 0);
} else {
printf("[+] 程序退出,代码: %d\n", exitCode);
}
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
} else {
printf("[-] 运行失败: %d\n", GetLastError());
}
}
// 主程序
int main(int argc, char* argv[]) {
printf("===========================================\n");
printf(" Complete Packer System \n");
printf("===========================================\n\n");
if (argc < 2) {
printf("用法:\n");
printf(" 加壳: %s pack <目标> <壳> <输出> [密钥]\n", argv[0]);
printf(" 脱壳: %s unpack <加壳程序> <输出> <OEP>\n", argv[0]);
printf(" 测试: %s test <原始> <加壳>\n", argv[0]);
return 1;
}
if (strcmp(argv[1], "pack") == 0 && argc >= 5) {
PACKER_CONTEXT ctx;
if (InitPacker(&ctx, argv[2], argv[3])) {
strcpy(ctx.outputPath, argv[4]);
ctx.encryptionType = ENCRYPT_XOR;
ctx.encryptionKey = (argc > 5) ? strtoul(argv[5], NULL, 16) : 0x12345678;
PackExecutable(&ctx);
CleanupPacker(&ctx);
}
}
else if (strcmp(argv[1], "unpack") == 0 && argc >= 5) {
Unpack(argv[2], argv[3], strtoul(argv[4], NULL, 16));
}
else if (strcmp(argv[1], "test") == 0 && argc >= 4) {
TestPackedFile(argv[2], argv[3]);
}
return 0;
}
6、课后作业
-
完善加壳系统
- 添加压缩功能
- 支持DLL加壳
- 添加更多加密算法
-
增强反分析能力
- 添加多种反调试
- 添加反虚拟机
- 添加代码混淆
-
开发脱壳工具
- 自动查找OEP
- 重建IAT
- 修复各种PE问题