首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
加壳脱壳
发新帖
2
2
[原创]压缩壳的简单实现
发表于: 2026-7-10 15:55
3859
[原创]压缩壳的简单实现
dong1e
2026-7-10 15:55
3859
## 压缩壳的简单实现 ### 0x01 实现原理 实现一个压缩壳,需要做三步操作,三步过程涉及比较深入的PE结构理解,最终的实现目标如下图所示  其中过程分为以下三步 1、对原有数据压缩 对源程序使用算法进行压缩,并将数据放至目标程序的节表数据二的位置 2、对原有数据还原 编写壳代码,对自身节表二的数据进行压缩算法还原,并将生成的PE文件放至内存或者节表数据一的位置 3、对还原后的数据加载至内存并运行 编写LoadPE加载程序,对加载至内存的代码进行加载 ### 0x02 程序入口编写 需要编写一个工具程序,将源目标程序变为目标程序的样子,该程序可以为控制台,也可以为其他程序,本文为了方便,编写为MFC程序 1、创建MFC程序 此处选择基于对话框的设计,使用MFC选择在静态库中使用MFC,使用静态库可以将其他库打包进入该程序,相对于共享链接DLL更干净,迁移更方便  2、添加对应的窗体功能 - Mfc Edit Browser Control组件,用于选择打开的文件路径,ID此处命名为MEDIT_FILE_PATH  - Button Control,用于加壳的入口,此处命名为BTN_PACK,并右键该按钮,添加点击事件   3、添加窗体代码 ```c++ void CPackerDlg::OnBnClickedPack() { // TODO: 在此添加控件通知处理程序代码 CString strPath; GetDlgItemText(MEDIT_FILE_PATH, strPath); CString strNewPath = strPath.Left(strPath.ReverseFind('.')) + "_pack.exe"; //加壳 CPacker packer; if (packer.Pack(strPath, strNewPath)) { AfxMessageBox("OK",MB_OK,0); } else { AfxMessageBox("False"); } } ``` ### 0x03 分析EXE程序,将将源程序进行压缩 #### 1、分析EXE程序 对传入的文件进行解析,并将其存入变量 ```C++ bool CPacker::AnalysisPE(CString strSrc) { //创建文件句柄 m_hFile = CreateFile(strSrc, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); //获取原始PE文件大小 m_dwsrcPESize = GetFileSize(m_hFile, NULL); //创建原始文件的映射文件 m_hFileMap = CreateFileMapping( __in m_hFile, __in_opt NULL, __in PAGE_READONLY, __in 0, __in 0, __in_opt NULL ); //将映射对象挂载到当前进程虚拟地址空间 m_srcPE = (LPBYTE) MapViewOfFile( __in m_hFileMap, __in FILE_MAP_READ, __in 0, __in 0, __in 0 ); //解析原始文件的DOS头部,PE头部和节表数据 m_pDosHeader = (PIMAGE_DOS_HEADER)m_srcPE; m_pNTHeader = (PIMAGE_NT_HEADERS)(m_srcPE + m_pDosHeader->e_lfanew); m_pSecHeader = (PIMAGE_SECTION_HEADER)((LPBYTE)&m_pNTHeader->OptionalHeader + m_pNTHeader->FileHeader.SizeOfOptionalHeader); return true; } ``` #### 2、获取压缩数据 使用WinAPI的Compress函数对原始数据进行压缩,得到压缩数据m_pComData ```c++ bool CPacker::GetCompressData() { //创建压缩算法 COMPRESSOR_HANDLE h_Compress; CreateCompressor( COMPRESS_ALGORITHM_XPRESS_HUFF, NULL, &h_Compress ); //对原始数据进行压缩,多出的 28 字节是程序自定义压缩文件头,用来存放魔数、大小、校验等解压所需信息,感兴趣的同学可以自行搜索 m_pComData = new BYTE[m_dwsrcPESize + 28]; Compress( h_Compress, m_srcPE, //源PE指针 m_dwsrcPESize, //源PESize m_pComData, //压缩后数据指针 m_dwsrcPESize + 28, //压缩后缓冲区数据大小 &m_dwComDataSize //压缩后真实数据大小 ); return true; } ``` ### 0x04、壳代码的编写 壳代码主要做两件事 - 完成PE的解压缩 - 进行PE的加载 #### 1、完成PE的解压缩 创建的PE文件,可能有基址随机化,无重定位修正机制等问题,为了提升壳代码的健壮性,需要用地址无关代码。 我们需要做以下几步 - 通过运行环境获取Kernel32的地址 - 获取GetProcess的函数地址 - 通过GetProcess获取需要用到的函数地址 - 通过函数进行解压缩操作 ##### 1.1、获取Kernel32的库地址 进程在加载时,FS寄存器指向当前活动线程的TEB结构,通过TEB的[0x30]获取到对应的TEB结构,再通过TEB拿到加载的Kernel32的基址 ```c HMODULE GetKernel32() { HMODULE hKer; __asm { // 获取当前进程PEB结构体的内存地址 mov eax, dword ptr fs:[0x30] // 获取PEB_LDR_DATA 结构体地址 mov eax, dword ptr[eax + 0x0C] // 获取InLoadOrderModuleList链表头 mov eax, dword ptr[eax + 0x0C] // ntdll.dll 的模块结构体首地址 mov eax, dword ptr[eax] // kernel32.dll 的模块结构体首地址 mov eax, dword ptr[eax] // kernel32.dll 的内存基址 mov eax, dword ptr[eax + 0x18] mov hKer, eax } return hKer; } ``` ##### 1.2、获取在内存中各个函数的真实地址 - 创建函数指针 ```c++ // 创建函数声明 // GetProceAddress函数 typedef FARPROC (WINAPI* PFN_GetProcAddress) ( HMODULE hModule, LPCSTR lpProcName ); // LoadLibrary函数 typedef HMODULE(WINAPI* PFN_LoadLibraryA)( _In_ LPCSTR lpLibFileName ); // CreateDeCompressor函数 typedef BOOL(WINAPI* PFN_CreateDeCompressor)( DWORD Algorithm, PCOMPRESS_ALLOCATION_ROUTINES AllocationRoutines, PDECOMPRESSOR_HANDLE DeCompressHandle ); // DeCompress函数 typedef BOOL(WINAPI* PFN_DeCompress)( DECOMPRESSOR_HANDLE DeCompressorHandle, LPCVOID CompressedData, SIZE_T CompressedDataSize, PVOID UnCompressedBuffer, SIZE_T UnComressedBufferSize, PSIZE_T UnCompressedDataSize ); // VirtualAlloc函数 typedef LPVOID (WINAPI* PFN_VirtualAlloc)( LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect ); // VirtualProtect函数 typedef BOOL(WINAPI* PFN_VirtualProtect)( _In_ LPVOID lpAddress, _In_ SIZE_T dwSize, _In_ DWORD flNewProtect, _Out_ PDWORD lpflOldProtect ); ``` 此处为了使用方便,建了一个函数指针的结构体 ```c struct Environment { PFN_GetProcAddress pfnGetProcAddress; PFN_LoadLibraryA pfnLoadLibraryA; PFN_CreateDeCompressor pfnDeCompressor; PFN_DeCompress pfnDeCompress; PFN_VirtualAlloc pfnVirtualAlloc; PFN_VirtualProtect pfnVirtualProtect; }; ``` - 将函数指针指向对应的真实函数地址 Kernel32函数基址和想要的名称我们有了,剩下的就是找到对应的函数,需要用到导出表的知识 ```c FARPROC GetAddressFromModuleBase(HMODULE hModuleBase, LPCSTR lpProcName) { //解析DLL,找到对应的导出目录表 PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hModuleBase; PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((LPBYTE)pDosHeader + pDosHeader->e_lfanew); PIMAGE_SECTION_HEADER pSecHeader = (PIMAGE_SECTION_HEADER)((LPBYTE)&pNTHeader->OptionalHeader + pNTHeader->FileHeader.SizeOfOptionalHeader); PIMAGE_DATA_DIRECTORY pDataDir = (PIMAGE_DATA_DIRECTORY)&(pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]); PIMAGE_EXPORT_DIRECTORY pExpDir = (PIMAGE_EXPORT_DIRECTORY)((DWORD_PTR)hModuleBase + pDataDir->VirtualAddress); // 获取导出名称表,名称序号表和导出地址表 DWORD* pNames = (DWORD*)((DWORD)hModuleBase + pExpDir->AddressOfNames); WORD* pOrdinals = (WORD*)((DWORD)hModuleBase + pExpDir->AddressOfNameOrdinals); DWORD* pFuncs = (DWORD*)((DWORD)hModuleBase + pExpDir->AddressOfFunctions); // 针对名称找到对应的RVA地址 // 判断 lpProcName 是序号(高16位为0)还是名称指针 if (((DWORD_PTR)lpProcName & 0xFFFF0000) == 0) { // 按序号查找 WORD ordinal = (WORD)(DWORD_PTR)lpProcName; if (ordinal < pExpDir->Base || ordinal >= pExpDir->Base + pExpDir->NumberOfFunctions) return NULL; DWORD index = ordinal - pExpDir->Base; DWORD funcRva = pFuncs[index]; if (funcRva >= pDataDir->VirtualAddress && funcRva < pDataDir->VirtualAddress + pDataDir->Size) return NULL; // 转发函数 return (FARPROC)((DWORD_PTR)hModuleBase + funcRva); } else { // 按名称查找 for (DWORD i = 0; i < pExpDir->NumberOfNames; i++) { char* funcName = (char*)((DWORD_PTR)hModuleBase + pNames[i]); if (CmpStr(funcName, lpProcName) == TRUE) { DWORD funcRva = pFuncs[pOrdinals[i]]; if (funcRva >= pDataDir->VirtualAddress && funcRva < pDataDir->VirtualAddress + pDataDir->Size) return NULL; return (FARPROC)((DWORD_PTR)hModuleBase + funcRva); } } } return NULL; } ``` 进行函数调用 ```c VOID InitEnvironment(Environment* pEnv) { // 获取Kernel32基址 HMODULE hKer = GetKernel32(); // 保存局部变量,写数组方式可以存在栈上,全局变量和局部变量都会和地址相关 char szGetProcAddress[] = { 'G','e','t','P','r','o','c','A','d','d','r','e','s','s','\0' }; char szLoadLibraryA[] = { 'L','o','a','d','L','i','b','r','a','r','y','A','\0' }; char szVirtualAlloc[] = { 'V','i','r','t','u','a','l','A','l','l','o','c','\0' }; char szVirtualProtect[] = { 'V','i','r','t','u','a','l','P','r','o','t','e','c','t','\0' }; char szCabinetDll[] = { 'C','a','b','i','n','e','t','.','d','l','l','\0' }; char szCreateCompressor[] = { 'C','r','e','a','t','e','D','e','c','o','m','p','r','e','s','s','o','r','\0' }; char szDeCompress[] = { 'D','e','c','o','m','p','r','e','s','s','\0' }; // 获取GetProcAddress、LoadLibrary、VirtualAlloc和VirtualProtect的函数地址 pEnv->pfnGetProcAddress = (PFN_GetProcAddress)GetAddressFromModuleBase(hKer, szGetProcAddress); pEnv->pfnLoadLibraryA = (PFN_LoadLibraryA)GetAddressFromModuleBase(hKer, szLoadLibraryA); pEnv->pfnVirtualAlloc = (PFN_VirtualAlloc)GetAddressFromModuleBase(hKer, szVirtualAlloc); pEnv->pfnVirtualProtect = (PFN_VirtualProtect)GetAddressFromModuleBase(hKer, szVirtualProtect); // 加载Cabinet库和里边的解压缩函数 HMODULE hCabinet = pEnv->pfnLoadLibraryA(szCabinetDll); pEnv->pfnDeCompressor = (PFN_CreateDeCompressor)GetAddressFromModuleBase(hCabinet, szCreateCompressor); pEnv->pfnDeCompress = (PFN_DeCompress)GetAddressFromModuleBase(hCabinet, szDeCompress); } ``` ##### 1.3、进行解压缩 首先需要获取当前程序的模块基址,和Kernel32的模块基址获取类似,通过TEB找到PEB,再通过PEB找到模块基址 ```c HMODULE GetUserModuleBase() { HMODULE hUser; __asm { mov eax, dword ptr fs : [0x30] mov eax, dword ptr[eax + 0x0C] mov eax, dword ptr[eax + 0x0C] mov eax, dword ptr[eax + 0x18] mov hUser, eax } return hUser; } ``` 获取完用户的基址后,再对数据节的代码进行解压缩 ```c // 初始化上一步的函数 Environment env; InitEnvironment(&env); // 定位程序的数据节 LPBYTE pImageBase = (LPBYTE)GetUserModuleBase(); PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pImageBase; PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((LPBYTE)pDosHeader + pDosHeader->e_lfanew); PIMAGE_SECTION_HEADER pSecHeader = (PIMAGE_SECTION_HEADER)((LPBYTE)&pNTHeader->OptionalHeader + pNTHeader->FileHeader.SizeOfOptionalHeader); LPBYTE pComData = pImageBase + pSecHeader[1].VirtualAddress; DWORD dwComDataSize = pSecHeader[1].PointerToRelocations; DWORD dwSrcPESize = pSecHeader[1].PointerToLinenumbers; //解压缩 LPBYTE pPEBuffer = (LPBYTE)env.pfnVirtualAlloc( NULL, dwSrcPESize, MEM_COMMIT, PAGE_READWRITE ); DECOMPRESSOR_HANDLE hDeCompressor; env.pfnDeCompressor( COMPRESS_ALGORITHM_XPRESS_HUFF, NULL, &hDeCompressor); DWORD dwDeCompressDataSize; env.pfnDeCompress( hDeCompressor, pComData, //压缩数据 dwComDataSize, //压缩数据大小 pPEBuffer, //解压缩缓冲区 dwSrcPESize, //解压缩缓冲区大小 &dwDeCompressDataSize ); ``` #### 2、将解压缩后的数据进行LoadPE操作 LoadPE是模仿操作系统将PE文件按照特定格式加载进内存的操作,并将EIP执行新PE的入口点 PE加载分为两种,一种为进程镂空操作,另一种为反射式加载,这边采用反射式加载的方式,申请一块新内存,并手动解析PE,将其运行。 LoadPE分为以下几步 ##### 2.1、解析PE ```c // 基于PE的基址,进行解析 LPVOID pFileMap = (PVOID)pPEBuffer; PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pPEBuffer; PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((BYTE*)pDosHeader + pDosHeader->e_lfanew); DWORD dwHeaderSize = pNTHeader->OptionalHeader.SizeOfHeaders; DWORD dwImageSize = pNTHeader->OptionalHeader.SizeOfImage; PIMAGE_DATA_DIRECTORY pDataDir = (PIMAGE_DATA_DIRECTORY)pNTHeader->OptionalHeader.DataDirectory; PIMAGE_SECTION_HEADER pSecHeader = IMAGE_FIRST_SECTION(pNTHeader); DWORD dwBaseRelocationRva = pDataDir[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress; DWORD dwBaseRelocationSize = pDataDir[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size; DWORD dwNumSecSize = (DWORD)pNTHeader->FileHeader.NumberOfSections; ``` 解析完成后,获取到对应的ImageSize,并使用VirtualAlloc分配内存,尝试使用PE自带的ImageBase,如果能分配成功,则分配至该内存,否则随机分配 ```c // 分配新内存:先尝试PE指定的ImageBase,失败则由OS自选 LPVOID hNewBase = pEnv->pfnVirtualAlloc( (LPVOID)pNTHeader->OptionalHeader.ImageBase, dwImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE ); if (hNewBase == NULL) { hNewBase = pEnv->pfnVirtualAlloc( NULL, dwImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE ); } ``` 基于新的PE基址,算出偏移值和新的入口点地址 ```c DWORD dwRelocBase = (DWORD)hNewBase - pNTHeader->OptionalHeader.ImageBase; DWORD dwPEEntry = (DWORD)hNewBase + pNTHeader->OptionalHeader.AddressOfEntryPoint; ``` ##### 2.2、拷贝文件头数据 ```c // 拷贝PE头到新内存 memcpy(hNewBase, pDosHeader, dwHeaderSize); ``` ##### 2.3、拷贝节数据 ```c // 逐节从文件偏移拷贝到新内存RVA for (DWORD i = 0; i < dwNumSecSize; i++) { PIMAGE_SECTION_HEADER pSection = pSecHeader + i; // SizeOfRawData为0无需拷贝(如.bss节) if (pSection->SizeOfRawData == 0) continue; LPVOID dwDst = (char*)hNewBase + pSection->VirtualAddress; LPVOID dwSrc = (char*)pFileMap + pSection->PointerToRawData; // SizeOfRawData可能因文件对齐大于VirtualSize,取小值防止越界 DWORD dwCopySize = pSection->SizeOfRawData; if (dwCopySize > pSection->Misc.VirtualSize) dwCopySize = pSection->Misc.VirtualSize; memcpy(dwDst, dwSrc, dwCopySize); } ``` ##### 2.4、修复导入表 通过NTHeader和导入表地址算出导出节表头 ```c PIMAGE_SECTION_HEADER GetSectionHeader(DWORD RVA, PIMAGE_NT_HEADERS pNTHeader) { PIMAGE_SECTION_HEADER pSecHeader = IMAGE_FIRST_SECTION(pNTHeader); DWORD dwNumOfSections = pNTHeader->FileHeader.NumberOfSections; for (DWORD i = 0; i < dwNumOfSections; i++) { if (RVA >= pSecHeader->VirtualAddress && RVA < pSecHeader->VirtualAddress + pSecHeader->Misc.VirtualSize) { return pSecHeader; } pSecHeader++; } return NULL; } ``` 进行导入表修复 ```c // 导入表修复 DWORD dwImportAddr = pDataDir[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; // 无导入表时RVA为0,跳过避免GetSectionHeader返回NULL崩溃 // DWORD dwDiff = GetSectionHeader(dwImportAddr, pNTHeader)->VirtualAddress - GetSectionHeader(dwImportAddr, pNTHeader)->PointerToRawData; if (dwImportAddr != 0) { PIMAGE_SECTION_HEADER pImpSecHdr = GetSectionHeader(dwImportAddr, pNTHeader); if (pImpSecHdr != NULL) { // RVA转文件偏移的基值:SectionRawBase + (RVA - SectionRvaBase) DWORD dwImpSecRawBase = pImpSecHdr->PointerToRawData; DWORD dwImpSecRvaBase = pImpSecHdr->VirtualAddress; // dwDiff = dwImportAddr - dwDiff; // PIMAGE_IMPORT_DESCRIPTOR pItem = PIMAGE_IMPORT_DESCRIPTOR((DWORD)pFileMap + dwDiff); PIMAGE_IMPORT_DESCRIPTOR pItem = PIMAGE_IMPORT_DESCRIPTOR((DWORD)pFileMap + dwImpSecRawBase + (dwImportAddr - dwImpSecRvaBase)); while (true) { if (pItem->Name == NULL || pItem->FirstThunk == NULL) break; // HMODULE hDll = pEnv->pfnLoadLibraryA((LPCSTR)((DWORD)pFileMap + pItem->Name - dwDiff)); HMODULE hDll = pEnv->pfnLoadLibraryA((LPCSTR)((DWORD)pFileMap + dwImpSecRawBase + (pItem->Name - dwImpSecRvaBase))); if (hDll == NULL) break; PDWORD pINT = (PDWORD)pItem->FirstThunk; if (pItem->OriginalFirstThunk != NULL) pINT = (PDWORD)pItem->OriginalFirstThunk; // pINT = (PDWORD)((DWORD)pFileMap + (DWORD)pINT - dwDiff); pINT = (PDWORD)((DWORD)pFileMap + dwImpSecRawBase + ((DWORD)pINT - dwImpSecRvaBase)); DWORD dwIATAddr = pItem->FirstThunk + (DWORD)hNewBase; while (*pINT != NULL) { DWORD dwFuncAddr = 0; if (*pINT & 0x80000000) { dwFuncAddr = (DWORD)pEnv->pfnGetProcAddress(hDll, (LPCSTR)LOWORD(*pINT)); } else { DWORD dwNameRva = sizeof(WORD) + *(PDWORD)pINT; LPCSTR lpName = (LPCSTR)((DWORD)pFileMap + dwImpSecRawBase + (dwNameRva - dwImpSecRvaBase)); dwFuncAddr = (DWORD)pEnv->pfnGetProcAddress(hDll, lpName); } *(DWORD*)dwIATAddr = dwFuncAddr; pINT++; dwIATAddr += 4; } pItem++; } } } ``` ##### 2.5、修复重定位表 ```C // 重定位表修复 // 无重定位表时跳过(EXE通常无重定位),避免GetSectionHeader返回NULL崩溃 if (dwBaseRelocationRva != 0 && dwBaseRelocationSize != 0) { // PIMAGE_SECTION_HEADER pBaseSecHdr = GetSectionHeader((DWORD)pBaseRelocation, pNTHeader); PIMAGE_SECTION_HEADER pBaseSecHdr = GetSectionHeader(dwBaseRelocationRva, pNTHeader); if (pBaseSecHdr != NULL) { // pBaseRelocation = (PIMAGE_BASE_RELOCATION)((DWORD)pFileMap + (DWORD)pBaseRelocation - pBaseSecHdr->VirtualAddress); // RVA转文件偏移:SectionRawBase + (RVA - SectionRvaBase) PIMAGE_BASE_RELOCATION pBaseRelocation = (PIMAGE_BASE_RELOCATION)((DWORD)pFileMap + pBaseSecHdr->PointerToRawData + (dwBaseRelocationRva - pBaseSecHdr->VirtualAddress)); DWORD dwReadRelocSize = 0; for (; dwReadRelocSize < dwBaseRelocationSize; ) { DWORD dwPageOff = pBaseRelocation->VirtualAddress; LPWORD pRelocData = (LPWORD)((DWORD)pBaseRelocation + sizeof(IMAGE_BASE_RELOCATION)); DWORD dwItemCount = (pBaseRelocation->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD); for (size_t i = 0; i < dwItemCount; i++) { WORD wData = pRelocData[i]; if (wData & 0x3000) { DWORD dwAddr = (DWORD)hNewBase + dwPageOff + (wData & 0x0FFF); *(DWORD*)dwAddr += dwRelocBase; } } dwReadRelocSize += pBaseRelocation->SizeOfBlock; pBaseRelocation = (PIMAGE_BASE_RELOCATION)((DWORD)pBaseRelocation + pBaseRelocation->SizeOfBlock); } } } ``` 最后跳转至入口点 ``` DWORD OEP = EnrtyFromPEBuffer(pPEBuffer, &env); __asm { jmp OEP } ``` ### 0x05、组装PE文件 写一个壳代码的PE文件分为以下几步 - 创建文件 - 写PE头 - 写空节 - 写数据节(将原始PE压缩后的数据) - 写代码节(第三节的壳代码,将数据进行解压缩,并进行LoadPE操作) ##### 1、创建文件 使用WinAPI直接创建即可 ``` //创建文件 HANDLE hFile = CreateFile( strNewPE, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); ``` ##### 2、写PE头 PE头主要关注PE内容和对应的写入长度,需要对原始PE文件做解析,获取原始的Header大小和原始的PE文件 ``` m_dwNewPEHeaderSize = m_pNTHeader->OptionalHeader.SizeOfHeaders; m_pNewPEHeader = new BYTE[m_dwNewPEHeaderSize]; ZeroMemory(m_pNewPEHeader, m_dwNewPEHeaderSize); CopyMemory(m_pNewPEHeader, m_pDosHeader, m_dwNewPEHeaderSize); ``` 同时需要对新的PE头进行修改部分数据 - 节的数量 - PE入口地址 - 映像文件大小 - 对数据目录表置空 - 节表数据重写 ```C //对新的PE进行解析 PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)m_pNewPEHeader; PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((LPBYTE)pDosHeader + pDosHeader->e_lfanew); PIMAGE_SECTION_HEADER pSecHeader = (PIMAGE_SECTION_HEADER)((LPBYTE)&pNTHeader->OptionalHeader + pNTHeader->FileHeader.SizeOfOptionalHeader); DWORD dwsrcSecNum = pNTHeader->FileHeader.NumberOfSections; // 修改节数量 pNTHeader->FileHeader.NumberOfSections = 3; // 修改入口点 pNTHeader->OptionalHeader.AddressOfEntryPoint = m_pNewSecHeader[2].VirtualAddress; // 修改镜像大小 pNTHeader->OptionalHeader.SizeOfImage = m_pNewSecHeader[2].VirtualAddress + m_pNewSecHeader[2].Misc.VirtualSize; // 重置数据目录表 ZeroMemory(pNTHeader->OptionalHeader.DataDirectory, sizeof(pNTHeader->OptionalHeader.DataDirectory)); //重置节表 ZeroMemory(pSecHeader, dwsrcSecNum*40); // 节表数据修改 // 空节 strcpy((char*)m_pNewSecHeader[0].Name, ".CodeSec"); m_pNewSecHeader[0].Misc.VirtualSize = m_pNTHeader->OptionalHeader.SizeOfImage; m_pNewSecHeader[0].VirtualAddress = m_pSecHeader[0].VirtualAddress; m_pNewSecHeader[0].SizeOfRawData = 0; m_pNewSecHeader[0].PointerToRawData = 0; m_pNewSecHeader[0].Characteristics = IMAGE_SCN_MEM_WRITE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ; //数据节 strcpy((char*)m_pNewSecHeader[1].Name, ".DataSec"); m_pNewSecHeader[1].Misc.VirtualSize = GetAlign(m_dwComDataSize, m_pNTHeader->OptionalHeader.SectionAlignment); m_pNewSecHeader[1].VirtualAddress = m_pNewSecHeader[0].VirtualAddress + m_pNewSecHeader[0].Misc.VirtualSize; m_pNewSecHeader[1].SizeOfRawData = m_dwComDataSize; m_pNewSecHeader[1].PointerToRawData = m_pNTHeader->OptionalHeader.SizeOfHeaders; m_pNewSecHeader[1].Characteristics = IMAGE_SCN_MEM_WRITE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ; m_pNewSecHeader[1].PointerToRelocations = m_dwComDataSize; m_pNewSecHeader[1].PointerToLinenumbers = m_dwsrcPESize; //代码(壳)节 strcpy((char*)m_pNewSecHeader[2].Name, ".PackSec"); m_pNewSecHeader[2].Misc.VirtualSize = GetAlign(m_dwDeComCodeSize, m_pNTHeader->OptionalHeader.SectionAlignment); m_pNewSecHeader[2].VirtualAddress = m_pNewSecHeader[1].VirtualAddress + m_pNewSecHeader[1].Misc.VirtualSize; m_pNewSecHeader[2].SizeOfRawData = m_dwDeComCodeSize; // .PackSec 文件偏移对齐到 FileAlignment m_pNewSecHeader[2].PointerToRawData = GetAlign(m_pNewSecHeader[1].PointerToRawData + m_pNewSecHeader[1].SizeOfRawData, m_pNTHeader->OptionalHeader.FileAlignment); m_pNewSecHeader[2].Characteristics = IMAGE_SCN_MEM_WRITE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ; CopyMemory(pSecHeader, m_pNewSecHeader, sizeof(m_pNewSecHeader)); ``` ##### 3、写空节(.CodeSec) 空节无数据,需要在PE头将其内存映射准备好即可 ##### 4、写数据节(.DataSec) 将0x03节的压缩数据放至该节,并使用文件对其的方式进行对齐 ``` //写数据节 WriteFile(hFile, m_pComData, m_dwComDataSize, &dwBytesWrited, NULL); // 填充到 .PackSec 文件偏移(对齐到 FileAlignment) DWORD dwPadSize = m_pNewSecHeader[2].PointerToRawData - (m_pNewSecHeader[1].PointerToRawData + m_pNewSecHeader[1].SizeOfRawData); if (dwPadSize > 0) { LPBYTE pPad = new BYTE[dwPadSize]; ZeroMemory(pPad, dwPadSize); WriteFile(hFile, pPad, dwPadSize, &dwBytesWrited, NULL); delete[] pPad; } ``` ##### 5、写壳节(.PackSec) 壳代码需要将0x04章节的数据生成干净的代码 需要做以下步骤,确保代码入口点为自己的入口代码 - 开启release - 随机基址打开 - 链接器->高级->添加入口函数 - 链接器->高级->关闭数据执行保护(关闭DEP) - C++->代码生成->禁用安全检查(关闭GS) - 确保自己的函数位于代码段的开头,函数处添加`extern "C" __declspec(code_seg(".text$a")) Entry()` 最终的代码节如图所示  将生成的代码拷贝为C++代码  ```c const uint8_t data[1496] = { 0x55, 0x8B, 0xEC, 0x83, 0xEC, 0x44, 0x8D, 0x45, 0xBC, 0x50, 0xE8, 0x51, 0x04, 0x00, 0x00, 0x83, 0xC4, 0x04, 0xE8....} m_dwDeComCodeSize = sizeof(data); m_pDeComCode = new BYTE[m_dwDeComCodeSize]; memcpy(m_pDeComCode, data, m_dwDeComCodeSize); //写代码节 WriteFile(hFile, m_pDeComCode, m_dwDeComCodeSize, &dwBytesWrited, NULL); CloseHandle(hFile); ``` 完成后,即可完成一个简单的加壳程序 ### 0x06 演示效果 #### 1、静态PE效果 节数量  数据目录表  节表 有三个节,分别为代码节,数据节和壳节  节数据有两个节 1、数据节(原始PE的压缩数据)  2、代码节(壳代码)  #### 2、动态运行效果 内存布局 EIP指针指向.PackSec的入口   参照源码进行对照,前两个为初始化环境变量和获取用户模块基址  跟进初始换环境变量函数  获取kernel32模块基址  跟进调试,成功获取Kernel32基址   将局部变量存入栈   下边获取各个函数地址  GetProcAddress  LoadLibrary  VirtualAlloc  VirtualProtect  使用LoadLibrary加载cabinet库  获取CreateDecompressor函数  获取Decompress函数  下一步获取模块基址    内存如图所示  下一步解析PE数据,找到压缩数据入口和对应的大小,并对压缩数据进行解压缩  DosHeader + 3C的位置得到NTHeader  通过NTHeader的可选头位置+可选头大小得到节表起始位置  获取压缩数据的起始位置,大小  使用函数分配内存,并使用解压函数,将解压后的数据放至内存  成功分配内存,内存地址为0x001D0000  该内存暂时为空数据  后边分别调用创建解压器工具和进行解压操作 DeCompressor创建成功  下一步调用DeCompress函数,将数据段中的数据解压缩至0x001D0000位置处  下一步为模拟操作系统进行加载PE操作 加载PE分为一下几步 - 分配内存 - 在内存中按照格式映射镜像 - 修复导入表 - 修复重定位表 - 将指针执行代码入口点 1、分配内存 首先尝试在原始PE的ImageBase上分配内存,分配失败则自动分配  调试中,发现需要分配的内存地址为0x00400000  该地址分配失败  自动分配的地址为0x00560000  目前该内存区域为空  2、在内存中按照格式映射镜像 下一步,我们需要对文件头和节数据进行依次拷贝  文件头包括Dos头,DosStub,NT头和节表几块  根据映射图,这几块内存在内存和文件中的位置一致,我们只需要获取到SizeOfHeaders这个字段值,并将文件头开始指针,和长度进行拷贝即可 该PE的SizeOfHeaders值为0x400,可以看到0x400以上的内容都为文件头的数据  进行memcpy的调用,源地址为PE文件映射首地址0x001D0000,目的地址为新内存加载地址0x00560000,大小为0x400  拷贝后的内存映射情况   下一步为节数据的拷贝 我们需要根据节表的VirtualAddress、SizeOfRawData和PointerToRawData三个字段进行计算,将PointerToRawData的指向的起始数据,拷贝SizeOfRawData的长度到内存基址+VirtualAddress的位置   继续调试跟进内存 第一个节为.textbass段,数据大小为0,不拷贝数据  第二个节为.text段,需要拷贝的内存地址为0x00560000+11000h = 0x00571000,拷贝大小为6800h  观察内存变化   和文件中的数据相同  依次循环节表,将全部节数据拷贝过去 3、修复导入表和重定位表 将PE文件映射进内存后,并不能够直接使用,需要修复多个表,包括全局变量,使用的库函数,使用绝对地址的汇编代码,本文简单修复一下必要的导入表和重定位表,tls表,延迟导入表,绑定导入表等感兴趣的同学可以尝试一下 导入表的修复,需要遍历导入描述符表,遍历获取对应的Name,并通过LoadLibrary加载DLL,获取新的模块基址,并通过导入名称表(INT)按函数名 / 序号获取函数真实地址,逐一填入 IAT 对应位置  LoadLibrary函数加载Kernel32.dll的模块基址  成功获取到模块基址   后续通过INT的序号或者名字把IAT填充  使用GetProcAddress函数,依次将IAT填充  获取GetFileSize的函数地址  通过汇编,查看IAT地址位置ds:[edi]处,查看到0x75BC33A0为第一个函数CreateFileA的函数地址,0x75BC35D0为第二个函数GetFileSize的地址    按照这个逻辑,逐渐填充所有用到的IAT地址  下一步为重定位表的修复 计算的偏移值为上方的dwRelocBase的值  定位到重定位表,依次修复即可  5、将指针执行代码入口点 最后返回入口点,并使用汇编跳转至入口点  
登录后可查看完整内容
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
收藏
・
2
点赞
・
2
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
东关之南
感谢你的贡献,论坛因你而更加精彩!
2026-9-23 21:07
wx_晨梦
为你点赞!
2026-7-16 09:11
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
2
)
dong1e
雪 币:
204
活跃值:
(197)
能力值:
( LV3,RANK:20 )
在线值:
发帖
1
回帖
1
粉丝
2
关注
私信
dong1e
2
楼
源代码程序,欢迎各位大牛指导!
上传的附件:
Packer.zip
(147.25kb,16次下载)
2026-7-10 16:25
0
option
雪 币:
9429
活跃值:
(4409)
能力值:
( LV2,RANK:10 )
在线值:
发帖
40
回帖
780
粉丝
4
关注
私信
option
3
楼
都用什么软件了,看着挺好看
2026-7-11 16:27
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
dong1e
1
发帖
1
回帖
20
RANK
关注
私信
他的文章
[原创]压缩壳的简单实现
3849
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部