windows 加壳工具代码实现03 - 加壳逻辑实现 PACK.CPP

📅 2026/7/15 15:45:13
windows 加壳工具代码实现03 - 加壳逻辑实现 PACK.CPP
目录PACK.cpp壳代码源文件Pack.h部分Pack.cpp包含壳加密主功能新增头文件全局变量声明PE结构初始化异或加密- 壳代码实现的主要功能获取区段数据清空绑定导入表修复重定位Pack.cpp壳代码源文件作用这是壳Shell的真正实现代码。干嘛的里面实现了 Start() 函数壳的入口点。包含手动获取 API 地址走 PEB、异或解密、弹出 MessageBox 询问、跳转回原始 OEP 等核心逻辑。通过 #pragma comment(linker,/merge:...) 把所有段合并成一个 .text 段方便被提取并作为新区段注入。地位被加到目标程序里的保护壳是加壳后的程序真正先运行的代码。Pack.h部分EXTERN_C BOOL Pack(CString szFilePath, BYTE byXorCode);szFilePath 加壳的路径byXorCode 用什么代码进行加密#pragma once // 加壳 打包函数 -----这个就是加壳需要的主要程序 // 加壳中的路径 szFilePath 用什么代码进行加密 EXTERN_C BOOL Pack(CString szFilePath, BYTE byXorCode); //初始化PE EXTERN_C BOOL InitPE(CString szFilePath); //加密 EXTERN_C DWORD XorCode(BYTE byXorCode); //RvaToFoa EXTERN_C DWORD RvaToFoa(DWORD dwRva, PIMAGE_NT_HEADERS64 szBuffer); //获取区段数据 EXTERN_C DWORD GetSectionData(PBYTE lpImage, DWORD dwSectionIndex, PBYTE lpBuffer, DWORD dwCodeBaseRVA); //修复重定位 EXTERN_C VOID FixReloc(PBYTE lpImage, PBYTE lpCode, DWORD dwCodeRVA); //清空绑定导入表 EXTERN_C VOID ClearBundleImport(); //增加区段 EXTERN_C ULONGLONG AddSection(LPBYTE pBuffer, DWORD dwSize, PCHAR szSectionName);Pack.cpp包含壳加密主功能新增头文件#include pch.h #include Pack.h #include Psapi.h #pragma comment(lib,psapi.lib) #include ../Stub/Stub.h #pragma comment(lib,./x64/Release/Stub.lib)全局变量声明//文件对象 CFile FileObject; //文件指针 PBYTE pFileBase NULL; //文件尺寸 DWORD dwFileSize 0; //NT头 PIMAGE_NT_HEADERS64 pNt NULL; //文件对齐 DWORD dwFileAlign; //内存对齐 DWORD dwMemAlign; //镜像基址 ULONGLONG ullImageBase; //程序OEP DWORD dwOEP; //代码基址 DWORD dwCodeBase; //代码尺寸 DWORD dwCodeSize; //区段结构 PIMAGE_SECTION_HEADER pLastSection; //新的区段RVA DWORD dwNewSectionRVA; //新的入口 DWORD g_dwNewOEP;PE结构初始化这段代码要具体在分析一下 PE结构的分析最后一个区段新增一个区段也就是壳区段要保证每个区段都是对齐的也就是判断现有的区段是否是对齐的也就是需要在区段后面再放一个区段需要保证最后一个区段与新增的区段是对的 内存对齐所以我们需要判断最后一个区段也就是现有的区段是否是对齐的/** * 函数名称: InitPE * 函数功能: 初始化PE文件结构。 * 负责打开指定路径的PE文件将其读入内存 * 解析DOS头和NT头提取关键的全局变量如对齐方式、入口点、基址等 * 并计算最后一个节区的结束位置为后续的节区添加做准备。 * * 参数说明: * param CString szFilePath [输入] 需要处理的PE文件的完整路径字符串。 * * 返回值: * return BOOL 操作成功返回 TRUE失败返回 FALSE。 */ BOOL InitPE(CString szFilePath) { // 检查文件句柄是否无效 且 内存缓冲区指针不为空防止内存泄漏或重复打开 if (FileObject.m_hFile INVALID_HANDLE_VALUE pFileBase) { // 关闭当前打开的文件对象 FileObject.Close(); // 释放之前分配的内存缓冲区 delete pFileBase; // 将指针置空避免悬空指针 pFileBase NULL; } // 以只读模式打开指定路径的文件 FileObject.Open(szFilePath, CFile::modeRead); // 获取文件的总长度字节数 dwFileSize FileObject.GetLength(); // 根据文件大小动态分配内存缓冲区 pFileBase new BYTE[dwFileSize]; // 尝试将文件内容读取到分配的内存缓冲区中 if (FileObject.Read(pFileBase, (DWORD)dwFileSize)) { // 将内存基址强制转换为 DOS头指针用于访问DOS头信息 PIMAGE_DOS_HEADER pDos (PIMAGE_DOS_HEADER)pFileBase; // 根据DOS头中的 e_lfanew 偏移量定位并获取 NT头指针64位 pNt (PIMAGE_NT_HEADERS64)((ULONGLONG)pFileBase pDos-e_lfanew); // 从NT可选头中获取文件对齐值FileAlignment用于计算节区在磁盘上的大小 dwFileAlign pNt-OptionalHeader.FileAlignment; // 从NT可选头中获取内存对齐值SectionAlignment用于计算节区在内存中的大小 dwMemAlign pNt-OptionalHeader.SectionAlignment; // 获取PE文件的镜像基址ImageBase即程序加载到内存的首选地址 ullImageBase pNt-OptionalHeader.ImageBase; // 获取程序的入口点OEP即程序开始执行的相对虚拟地址 dwOEP pNt-OptionalHeader.AddressOfEntryPoint; // 获取代码节区的基址BaseOfCode dwCodeBase pNt-OptionalHeader.BaseOfCode; // 获取代码节区的总大小SizeOfCode dwCodeSize pNt-OptionalHeader.SizeOfCode; // 获取第一个节区头的指针 PIMAGE_SECTION_HEADER pSection IMAGE_FIRST_SECTION(pNt); // 计算并指向最后一个节区头之后的位置即新节区将要插入的位置 pLastSection pSection[pNt-FileHeader.NumberOfSections]; // 获取当前最后一个节区的虚拟大小VirtualSize DWORD dwVirtualSize pLastSection[-1].Misc.VirtualSize; // 如果最后一个节区的虚拟大小不能被内存对齐值整除 if (dwVirtualSize % dwMemAlign) { // 向上取整计算对齐后的虚拟大小 dwVirtualSize (dwVirtualSize / dwMemAlign 1) * dwMemAlign; } else { // 如果刚好整除则保持对齐后的值 dwVirtualSize (dwVirtualSize / dwMemAlign) * dwMemAlign; } // 计算新节区的相对虚拟地址RVA最后一个节区的RVA 对齐后的虚拟大小 dwNewSectionRVA pLastSection[-1].VirtualAddress dwVirtualSize; // 初始化成功返回 TRUE return TRUE; } // 文件读取失败返回 FALSE return FALSE; }异或加密- 壳代码实现的主要功能DWORD XorCode(BYTE byXorCode)壳代码实现对代码段加密的主要功能//-------------异或加密------------------- DWORD XorCode(BYTE byXorCode) { // 获取代码段的基地址 DWORD dwVirtualAddress dwCodeBase; // DWORD dwOffset RvaToFoa(dwCodeBase, pNt); //判断dwOffset 是否有效 if (!dwOffset) { // 获取第一个区段 PIMAGE_SECTION_HEADER pSection IMAGE_FIRST_SECTION(pNt); // 第一个区段穿进去获取到FOA dwOffset RvaToFoa(pSection[1].VirtualAddress, pNt); dwVirtualAddress pSection[1].VirtualAddress; } // XOR---- 加密操作 PBYTE pBase (PBYTE)((ULONGLONG)pFileBase dwOffset); //每个字段都进行异或加密 然后返回加密的区段地址 for (size_t i 0; i dwCodeSize; i) { pBase[i] ^ byXorCode; } // 返回区段加密的虚拟地址VA return dwVirtualAddress; }获取区段数据// 函数定义获取 PE 文件中指定节区的数据信息 // 返回值该节区在磁盘上的大小 (SizeOfRawData) DWORD GetSectionData(PBYTE lpImage, DWORD dwSectionIndex, PBYTE lpBuffer, DWORD dwCodeBaseRVA) { // 1. 将传入的文件基址转换为 DOS 头结构体指针 // 作用PE 文件开头是 DOS 头用于获取 e_lfanewNT 头在文件中的偏移位置 PIMAGE_DOS_HEADER pDos (PIMAGE_DOS_HEADER)lpImage; // 2. 定位 NT 头结构体 // 作用通过“基址 e_lfanew”找到 NT 头。 // 注意这里强转为 ULONGLONG 是为了在 64 位环境下防止指针加法溢出 // PIMAGE_NT_HEADERS64 表明该代码专门用于解析 64 位 PE 文件。 PIMAGE_NT_HEADERS64 pNt (PIMAGE_NT_HEADERS64)((ULONGLONG)lpImage pDos-e_lfanew); // 3. 获取节区表头指针 // 作用IMAGE_FIRST_SECTION 是一个宏它跳过 NT 头直接指向第一个节区头数组的起始位置。 PIMAGE_SECTION_HEADER pSectionHeader IMAGE_FIRST_SECTION(pNt); // 4. 获取指定节区在磁盘文件中的大小 // 作用pSectionHeader[dwSectionIndex] 定位到第 dwSectionIndex 个节 // SizeOfRawData 是该节在文件中对齐后的大小。 DWORD dwSize pSectionHeader[dwSectionIndex].SizeOfRawData; // 5. 计算并返回该节区数据在内存中的起始地址 // 作用基址 节区的虚拟地址 (VirtualAddress)得到该节数据在内存中的实际指针赋值给引用参数 lpBuffer。 lpBuffer (PBYTE)(lpImage pSectionHeader[dwSectionIndex].VirtualAddress); // 6. 获取并返回该节区的相对虚拟地址 (RVA) // 作用将节区的 VirtualAddress 赋值给引用参数 dwCodeBaseRVA供外部调用者使用。 dwCodeBaseRVA pSectionHeader[dwSectionIndex].VirtualAddress; // 注意原代码缺少 return 语句根据函数定义此处应返回 dwSize // return dwSize; }清空绑定导入表// 函数名称: ClearBundleImport // 功能: 清除 PE 文件结构中的“绑定导入表”Bound Import目录信息通常用于解绑或剥离导入表相关数据 VOID ClearBundleImport() { // 将文件基址 pFileBase 转换为 DOS 文件头指针用于访问 PE 文件的基本结构 PIMAGE_DOS_HEADER pDos (PIMAGE_DOS_HEADER)pFileBase; // 根据 DOS 头中的 e_lfanew 字段指向 NT 头的偏移计算出 NT 头64 位的地址 PIMAGE_NT_HEADERS64 pNt (PIMAGE_NT_HEADERS64)((ULONGLONG)pFileBase pDos-e_lfanew); // 获取 NT 头中可选头Optional Header里的数据目录Data Directory数组指针 PIMAGE_DATA_DIRECTORY pDir pNt-OptionalHeader.DataDirectory; // 将数据目录数组中“绑定导入表”Bound Import对应的条目清零即移除绑定导入信息 ZeroMemory((pDir[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT]), sizeof(IMAGE_DATA_DIRECTORY)); }修复重定位// 函数名称: FixReloc // 功能: 修复 PE 文件的重定位表。该函数遍历重定位表根据新的代码基址dwCodeRVA和全局镜像基址ullImageBase计算并更新内存中的重定位地址。 // 参数: // - lpImage: 指向已加载到内存中的 PE 文件镜像基址的指针。 // - lpCode: 指向代码段的指针在此函数逻辑中似乎未直接使用可能是为了上下文保留。 // - dwCodeRVA: 代码段相对于镜像基址的新的相对虚拟地址偏移量。 VOID FixReloc(PBYTE lpImage, PBYTE lpCode, DWORD dwCodeRVA) { // 将传入的镜像基址转换为 DOS 头结构指针用于获取 PE 文件的基本信息 PIMAGE_DOS_HEADER pDos (PIMAGE_DOS_HEADER)lpImage; // 根据 DOS 头中的 e_lfanew 偏移量计算出 NT 头64位在内存中的地址 PIMAGE_NT_HEADERS64 pNt (PIMAGE_NT_HEADERS64)((ULONGLONG)lpImage pDos-e_lfanew); // 获取 NT 可选头中的数据目录数组指针该数组包含各种表如导入表、重定位表等的信息 PIMAGE_DATA_DIRECTORY pRelocDir pNt-OptionalHeader.DataDirectory; // 将指针移动到数据目录数组中的“基址重定位表”IMAGE_DIRECTORY_ENTRY_BASERELOC条目 pRelocDir (pRelocDir[IMAGE_DIRECTORY_ENTRY_BASERELOC]); // 根据重定位表的虚拟地址VirtualAddress计算出重定位表在内存中的实际起始位置 PIMAGE_BASE_RELOCATION pReloc (PIMAGE_BASE_RELOCATION)((ULONGLONG)lpImage pRelocDir-VirtualAddress); // 定义一个局部结构体 TYPEOFFSET用于解析重定位块中的具体条目高4位是类型低12位是偏移量 typedef struct { WORD Offset : 12; // 重定位项相对于当前块基址的偏移量 WORD Type : 4; // 重定位类型例如 IMAGE_REL_BASED_DIR64 }TYPEOFFSET, * PTYPEOFFSET; // 循环遍历重定位表直到遇到 VirtualAddress 为 0 的结束标记 while (pReloc-VirtualAddress) { // 获取当前重定位块后的第一个重定位项即跳过块头 IMAGE_BASE_RELOCATION PTYPEOFFSET pTypeOffset (PTYPEOFFSET)(pReloc 1); // 计算 IMAGE_BASE_RELOCATION 结构体本身的大小通常为 8 字节 ULONGLONG ullSize sizeof(IMAGE_BASE_RELOCATION); // 计算当前块中包含的重定位项的数量(块总大小 - 块头大小) / 每个项的大小(2字节) ULONGLONG ullCount (pReloc-SizeOfBlock - ullSize) / 2; // 遍历当前块内的所有重定位项 for (size_t i 0; i ullCount; i) { // 安全检查如果当前项的内容为 NULL则跳出循环防止非法数据 if (*(PULONGLONG)(pTypeOffset[i]) NULL) { break; } // 计算需要进行重定位的完整相对虚拟地址块基址 项偏移量 ULONGLONG ullRva pReloc-VirtualAddress pTypeOffset[i].Offset; // 计算该重定位地址在内存中的实际指针位置 PULONGLONG ullRelocAddr (PULONGLONG)((ULONGLONG)lpImage ullRva); // 拿到重定位的地址执行核心修复逻辑 // 计算公式(原始存储值 - 原始镜像基址 - 原始代码基址) 新代码RVA 新全局镜像基址 // 注意此处逻辑似乎是在做某种特定的地址转换或修复 ULONGLONG ullRelocCode *ullRelocAddr - pNt-OptionalHeader.ImageBase - pNt-OptionalHeader.BaseOfCode dwCodeRVA ullImageBase; // 将计算出的新地址值写回内存中的重定位位置 *ullRelocAddr ullRelocCode; } // 移动到下一个重定位块当前地址 当前块的大小 pReloc (PIMAGE_BASE_RELOCATION)((ULONGLONG)pReloc pReloc-SizeOfBlock); // 额外的安全检查如果下一个块的 VirtualAddress 是特定的非法值0xfdfdfdfd通常是内存释放后的填充值则终止循环 if (pReloc-VirtualAddress 0xfdfdfdfd) { break; } } }新增区段/** * 函数名称: AddSection * 函数功能: 向PE文件内存中的缓冲区添加一个新的节区Section。 * 该函数会更新PE头信息计算对齐后的尺寸分配虚拟地址和文件偏移 * 并将新的节区数据追加到文件末尾最后保存修改后的文件。 * * 参数说明: * param LPBYTE pBuffer [输入] 指向新节区内容的缓冲区指针即要写入的新代码或数据。 * param DWORD dwSize [输入] 新节区内容的实际大小字节数。 * param PCHAR szSectionName [输入] 新节区的名称字符串例如 .text, .data 等最多8字符。 * * 返回值: * return ULONGLONG 返回新添加节区的虚拟地址RVA。 */ ULONGLONG AddSection(LPBYTE pBuffer, DWORD dwSize, PCHAR szSectionName) { // 将PE头中的节区数量加1表示新增了一个节区 pNt-FileHeader.NumberOfSections; // 清空新节区头部的内存确保所有字段初始化为0 memset(pLastSection, 0, sizeof(IMAGE_SECTION_HEADER)); // 将传入的节区名称复制到节区头部的Name字段中限制长度为IMAGE_SIZEOF_SHORT_NAME strcpy_s((char*)pLastSection-Name, IMAGE_SIZEOF_SHORT_NAME, szSectionName); // 定义变量用于存储计算后的虚拟大小 DWORD dwVartualSize 0; // 定义变量用于存储计算后的文件 rawData 大小 DWORD dwSizeOfRawData 0; // 获取当前PE文件在内存中的总镜像大小 DWORD dwSizeOfImage pNt-OptionalHeader.SizeOfImage; // 开始计算对齐后的尺寸 { // 如果当前镜像大小不能被内存对齐值整除 if (dwSizeOfImage % dwMemAlign) { // 向上取整计算对齐后的新镜像基址大小 dwSizeOfImage (dwSizeOfImage / dwMemAlign 1) * dwMemAlign; } else { // 如果刚好整除则保持原值或者是向下取整逻辑上等同于保持 dwSizeOfImage (dwSizeOfImage / dwMemAlign) * dwMemAlign; } // 如果新节区的实际大小不能被内存对齐值整除 if (dwSize % dwMemAlign) { // 向上取整计算对齐后的虚拟大小VirtualSize dwVartualSize (dwSize / dwMemAlign 1) * dwMemAlign; } else { // 如果刚好整除则使用计算后的值 dwVartualSize (dwSize / dwMemAlign) * dwMemAlign; } // 如果新节区的实际大小不能被文件对齐值整除 if (dwSize % dwFileAlign) { // 向上取整计算对齐后的文件 rawData 大小SizeOfRawData dwSizeOfRawData (dwSize / dwFileAlign 1) * dwFileAlign; } else { // 如果刚好整除则使用计算后的值 dwSizeOfRawData (dwSize / dwFileAlign) * dwFileAlign; } } // 设置新节区的相对虚拟地址RVA即新的镜像大小位置 pLastSection-VirtualAddress dwSizeOfImage; // 设置新节区在文件中的偏移量PointerToRawData指向当前文件末尾 pLastSection-PointerToRawData dwFileSize; // 设置新节区在文件中的占用大小对齐后的值 pLastSection-SizeOfRawData dwSizeOfRawData; // 设置新节区在内存中的虚拟大小对齐后的值 pLastSection-Misc.VirtualSize dwVartualSize; // 设置节区属性标志位 (0xE0000040 通常表示可读、可写、可执行) pLastSection-Characteristics 0xE0000040; // 更新PE可选头中的镜像总大小加上新的虚拟大小 pNt-OptionalHeader.SizeOfImage dwSizeOfImage dwVartualSize; // 更新程序入口点OEP指向新添加的代码入口假设 g_dwNewOEP 是相对于新节区的偏移 pNt-OptionalHeader.AddressOfEntryPoint g_dwNewOEP pLastSection-VirtualAddress; // 获取原始文件的路径 CString csWriteFilePath FileObject.GetFilePath(); // 构造新的输出文件路径将原文件名后缀前的部分截取出来并拼接 _GrkPatk.exe CString WirteFilePath csWriteFilePath.Left(csWriteFilePath.ReverseFind(.)) _GrkPatk.exe; // 以创建/读写模式打开新文件 CFile hFile(WirteFilePath.GetBuffer(0), CFile::modeCreate | CFile::modeReadWrite); // 将修改了PE头包含新节区头的内存缓冲区写入文件 hFile.Write(pFileBase, dwFileSize); // 将文件指针移动到文件末尾 hFile.SeekToEnd(); // 将新节区的实际内容pBuffer写入文件末尾 hFile.Write(pBuffer, dwSize); // 返回新节区的虚拟地址RVA return pLastSection-VirtualAddress; }加壳打包函数// 函数名称: Pack // 功能: 对PE文件进行加壳打包处理通过XOR加密、重定位修复和添加新节来实现打包 // 参数: // - szFilePath: 待加壳的PE文件路径 // - byXorCode: 用于XOR加密的密钥字节 BOOL Pack(CString szFilePath, BYTE byXorCode) { // 初始化返回值为FALSE BOOL bRet FALSE; // 初始化PE文件加载到内存中 bRet InitPE(szFilePath); // 使用XOR加密函数处理密钥返回虚拟地址 DWORD dwVirtualAddress XorCode(byXorCode); // 加载壳代码的DLLStub.dll HMODULE hModule LoadLibrary(LStub.dll); // 获取壳代码DLL中的全局参数结构体指针 PGLOBAL_PARAM pstcParam (PGLOBAL_PARAM)GetProcAddress(hModule, g_stcParam); // 设置镜像基址 pstcParam-ullImageBase ullImageBase; // 设置代码大小 pstcParam-dwCodeSize dwCodeSize; // 设置原始入口点 pstcParam-dwOEP dwOEP; // 设置XOR加密密钥 pstcParam-byXorCode byXorCode; // 设置壳代码的起始虚拟地址 pstcParam-lpStartVA (PBYTE)dwVirtualAddress; // 初始化模块信息结构 MODULEINFO modInfo { 0 }; // 获取壳代码DLL的模块信息 GetModuleInformation(GetCurrentProcess(), hModule, modInfo, sizeof(MODULEINFO)); // 分配内存用于复制壳代码 PBYTE lpModule new BYTE[modInfo.SizeOfImage]; // 复制壳代码到新分配的内存 memcpy_s(lpModule, modInfo.SizeOfImage, hModule, modInfo.SizeOfImage); // 初始化代码节指针 PBYTE pCodeSection NULL; // 初始化代码基址RVA DWORD dwCodeBaseRva 0; // 获取壳代码的节数据 DWORD dwSize GetSectionData(lpModule, 0, pCodeSection, dwCodeBaseRva); // 修复壳代码的重定位信息 FixReloc(lpModule, pCodeSection, dwNewSectionRVA); // 计算壳代码的OEP RVA DWORD dwStubOEPRva pstcParam-dwStart - (DWORD)hModule; // 计算新的OEP DWORD dwNewOEP dwStubOEPRva - dwCodeBaseRva; // 设置全局新的OEP g_dwNewOEP dwNewOEP; // 清除DLL特性 pNt-OptionalHeader.DllCharacteristics 0; // 清除绑定导入表 ClearBundleImport(); // 添加新的节到PE文件 if (AddSection(pCodeSection, dwSize, GrkPack)) { // 设置返回值为TRUE bRet TRUE; } // 释放分配的内存 delete lpModule; // 将指针置为NULL lpModule NULL; // 返回结果 return bRet; }