服务热线
15527777548/18696195380
发布时间:2022-04-08
简要描述:
前言前段时间自己学习了一下壳的编写,发现网上相关资料不是非常全,而且讲解顺序不太对胃口。因此参考了《黑客免杀攻防》中的代码对DLL型壳编写的结构进行了一次归纳整理,并附...
前言
一、壳的简介
1.1 壳的简介
1.2 壳的原理
而加壳之后,程序的.text段就被加密了(有些壳还会加密IAT表,资源段等等),程序的入口点指向壳添加的代码的入口(Stub的入口),Stub将程序的更改恢复(例如解密.text,IAT等等),最后执行完后跳转到原始的OEP。
1.3 壳的编写流程以及其中的问题
二、壳代码的编写
该部分主要包括:
2.1 初始化操作
#pragma comment(linker, "/merge:.data=.text") // 将.data合并到.text#pragma comment(linker, "/merge:.rdata=.text") // 将.rdata合并到.text#pragma comment(linker, "/section:.text,RWE") // 将.text段的属性设置为可读、可写、可执行
2.2 获取加密.text时的必要信息
typedef struct _GLOBAL_PARAM{ BOOL bShowMessage; // 是否显示MessageBox DWORD dwOEP; // 程序入口点 PBYTE lpStartVA; // 起始.text段地址 PBYTE lpEndVA; // 结束.text段地址}GLOBAL_PARAM, *PGLOBAL_PARAM;extern "C"__declspec(dllexport) GLOBAL_PARAM g_stcParam;
之后我们手动定义一个入口函数,如我们前面所提到的原因所说,我们需要将dll的加载代码去掉,所以我们使用naked关键字修饰
#pragma comment(linker, "/entry:"StubEntryPoint"") // 指定程序入口函数为StubEntryPoint()void start(){//这里存放对被加壳文件的操作}void __declspec(naked) StubEntryPoint(){ __asm sub esp, 0x50; // 分配0x50的栈空间 start(); // 执行Stub代码 __asm add esp, 0x50; // 平衡堆栈 __asm retn;}
显然,我们的Stub的核心之一就是解密,我们需要编写一个解密函数,从导入变量中获取起始地址与结束地址,之后解密。
void Decrypt(){// 在导出的全局变量中读取需解密区域的起始与结束VA PBYTE lpStart = g_stcParam.lpStartVA; PBYTE lpEnd = g_stcParam.lpEndVA;while (lpStart < lpEnd) { *lpStart ^= 0x15; lpStart++; }}
这样我们的原始的Stub就完成了。
2.3 添加API调用功能
2.3.1 获取kernel32.dlll的基地址
kd> dt _tebnt!_TEB +0x000 NtTib : _NT_TIB +0x01c EnvironmentPointer : Ptr32 Void +0x020 ClientId : _CLIENT_ID +0x028 ActiveRpcHandle : Ptr32 Void +0x02c ThreadLocalStoragePointer : Ptr32 Void +0x030 ProcessEnvironmentBlock : Ptr32 _PEB
PEB在TEB偏移0x30的位置上,而查看PEB结构如下
kd> dt _pebnt!_PEB +0x000 InheritedAddressSpace : UChar +0x001 ReadImageFileExecOptions : UChar +0x002 BeingDebugged : UChar +0x003 SpareBool : UChar +0x004 Mutant : Ptr32 Void +0x008 ImageBaseAddress : Ptr32 Void +0x00c Ldr : Ptr32 _PEB_LDR_DATA
从PEB中获得_PEB_LDR_DATA,windbg查看LDR_DATA定义
0:000> dt 0x77847880 _PEB_LDR_DATA ntdll!_PEB_LDR_DATA +0x000 Length : 0x30 +0x004 Initialized : 0x1 '' +0x008 SsHandle : (null) +0x00c InLoadOrderModuleList : _LIST_ENTRY [ 0x3126f8 - 0x36bd60 ] +0x014 InMemoryOrderModuleList : _LIST_ENTRY [ 0x312700 - 0x36bd68 ] +0x01c InInitializationOrderModuleList : _LIST_ENTRY [ 0x312798 - 0x36bd70 ] +0x024 EntryInProgress : (null) +0x028 ShutdownInProgress : 0 '' +0x02c ShutdownThreadId : (null)
从MSDN上获得对InInitializationOrderModuleList的描述(MSDN上对_PEB_LDR_DATA定义与windbg上有所不同,此处以windbg为准)
The head of a doubly-linked list that contains the loaded modules for the process. Each item in the list is a pointer to an LDR_DATA_TABLE_ENTRY structure.
查看MSDN的LDR_DATA_TABLE_ENTRY的定义
typedef struct _LDR_DATA_TABLE_ENTRY {PVOID Reserved1[2];LIST_ENTRY InMemoryOrderLinks;PVOID Reserved2[2];PVOID DllBase;PVOID EntryPoint;PVOID Reserved3;UNICODE_STRING FullDllName;BYTE Reserved4[8];PVOID Reserved5[3];union {ULONG CheckSum;PVOID Reserved6;};ULONG TimeDateStamp;} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
此时我们就看到了我们希望得到的DllBase,我们可以遍历搜索,当名字与”kernel32.dll”重合时获取DllBase,而由于每个操作系统中的Dll加载顺序恒定,kernel32.dll一般都是第二个加载的。因此我们可以通过如下方式获取
extern DWORD GetKernel32Base(); // 获取Kernel32.dll的模块基址DWORD GetKernel32Base(){DWORD dwKernel32Addr = 0;__asm{push eaxmov eax,dword ptr fs:[0x30] // eax = PEB的地址mov eax,[eax+0x0C] // eax = 指向PEB_LDR_DATA结构的指针mov eax,[eax+0x1C] // eax = 模块初始化链表的头指针InInitializationOrderModuleListmov eax,[eax] // eax = 列表中的第二个条目mov eax,[eax+0x08] // eax = 获取到的Kernel32.dll基址mov dwKernel32Addr,eaxpop eax}return dwKernel32Addr;}
DWORD dwAddrBase = GetKernel32Base(); // 1. 获取DOS头、NT头PIMAGE_DOS_HEADER pDos_Header;PIMAGE_NT_HEADERS pNt_Header;pDos_Header = (PIMAGE_DOS_HEADER)dwAddrBase;pNt_Header = (PIMAGE_NT_HEADERS)(dwAddrBase + pDos_Header->e_lfanew); // 2. 获取导出表项PIMAGE_DATA_DIRECTORY pDataDir;PIMAGE_EXPORT_DIRECTORY pExport;pDataDir = pNt_Header->OptionalHeader.DataDirectory; pDataDir = &pDataDir[IMAGE_DIRECTORY_ENTRY_EXPORT];pExport = (PIMAGE_EXPORT_DIRECTORY)(dwAddrBase + pDataDir->VirtualAddress);...
然后我们查看IMAGE_EXPORT_DIRECTORY的结构,如下所示。此处需要注意的是AddressOf…三个都是指针
typedef struct _IMAGE_EXPORT_DIRECTORY {DWORD Characteristics;DWORD TimeDateStamp;WORD MajorVersion;WORD MinorVersion;DWORD Name;DWORD Base;DWORD NumberOfFunctions;DWORD NumberOfNames;DWORD AddressOfFunctions; // RVA from base of imageDWORD AddressOfNames; // RVA from base of imageDWORD AddressOfNameOrdinals; // RVA from base of image} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
三个指针结构如下所示
根据上述结构,我们可以根据函数名字遍历链表获得GetProcAddress地址
// 3、获取导出表的必要信息 DWORD dwModOffset = pExport->Name; // 模块的名称 DWORD dwFunCount = pExport->NumberOfFunctions; // 导出函数的数量 DWORD dwNameCount = pExport->NumberOfNames; // 导出名称的数量 PDWORD pEAT = (PDWORD)(dwAddrBase + pExport->AddressOfFunctions); // 获取地址表的RVA PDWORD pENT = (PDWORD)(dwAddrBase + pExport->AddressOfNames); // 获取名称表的RVA PWORD pEIT = (PWORD)(dwAddrBase + pExport->AddressOfNameOrdinals); //获取索引表的RVA// 4、获取GetProAddress函数的地址for (DWORD i = 0; i < dwFunCount; i++) {if (!pEAT[i]) {continue; }// 4.1 获取序号 DWORD dwID = pExport->Base + i;// 4.2 变量EIT 从中获取到 GetProcAddress的地址for (DWORD dwIdx = 0; dwIdx < dwNameCount; dwIdx++) {// 序号表中的元素的值 对应着函数地址表的位置if (pEIT[dwIdx] == i) {//根据序号获取到名称表中的名字 DWORD dwNameOffset = pENT[dwIdx]; char * pFunName = (char*)(dwAddrBase + dwNameOffset);//判断是否是GetProcAddress函数if (!strcmp(pFunName, "GetProcAddress")) {// 获取EAT的地址 并将GetProcAddress地址返回 DWORD dwFunAddrOffset = pEAT[i];return dwAddrBase + dwFunAddrOffset; } } } }
2.3.3获取API
LPGETPROCADDRESS g_funGetProcAddress = nullptr; LPLOADLIBRARYEX g_funLoadLibraryEx = nullptr; LPEXITPROCESS g_funExitProcess = nullptr; LPMESSAGEBOX g_funMessageBox = nullptr; LPGETMODULEHANDLE g_funGetModuleHandle = nullptr; LPVIRTUALPROTECT g_funVirtualProtect = nullptr; bool InitializationAPI(){ HMODULE hModule; // 1. 初始化基础API 这里使用的是LoadLibraryExW g_funGetProcAddress = (LPGETPROCADDRESS)GetGPAFunAddr(); g_funLoadLibraryEx = (LPLOADLIBRARYEX)g_funGetProcAddress((HMODULE)GetKernel32Base(), "LoadLibraryExW"); // 2. 初始化其他API hModule = NULL; if (!(hModule = g_funLoadLibraryEx(L"kernel32.dll", NULL, NULL))) return false; g_funExitProcess = (LPEXITPROCESS)g_funGetProcAddress(hModule, "ExitProcess"); hModule = NULL; if (!(hModule = g_funLoadLibraryEx(L"user32.dll", NULL, NULL))) return false; g_funMessageBox = (LPMESSAGEBOX)g_funGetProcAddress(hModule, "MessageBoxW"); hModule = NULL; if (!(hModule = g_funLoadLibraryEx(L"kernel32.dll", NULL, NULL))) return false; g_funGetModuleHandle = (LPGETMODULEHANDLE)g_funGetProcAddress(hModule, "GetModuleHandleW"); hModule = NULL; if (!(hModule = g_funLoadLibraryEx(L"kernel32.dll", NULL, NULL))) return false; g_funVirtualProtect = (LPVIRTUALPROTECT)g_funGetProcAddress(hModule, "VirtualProtect"); return true;
extern __declspec(dllexport) GLOBAL_PARAM g_stcParam={0};void start(){// 1. 初始化所有APIif (!InitializationAPI()) return; // 2. 解密宿主程序 Decrypt(); // 3. 跳转到OEP __asm jmp g_stcParam.dwOEP; }
这样一来我们就完成了DLL的Stub部分
三、Stub的植入
之后我们就需要将Stub植入了。
但在此之前我们需要先将带加壳文件加载入内存并预处理文件
3.1 加载目标文件
// 2. 获取文件信息,并映射进内存中if (INVALID_HANDLE_VALUE == (hFile_In = CreateFile(strPath, GENERIC_READ, FILE_SHARE_READ,NULL, OPEN_EXISTING, 0, NULL))) {return false; }if (INVALID_FILE_SIZE == (dwFileSize = GetFileSize(hFile_In, NULL))) { CloseHandle(hFile_In);return false; }if (!(lpFileImage = VirtualAlloc(NULL, dwFileSize * 2, MEM_COMMIT, PAGE_READWRITE))) { CloseHandle(hFile_In);return false; } DWORD dwRet;if (!ReadFile(hFile_In, lpFileImage, dwFileSize, &dwRet, NULL)) { CloseHandle(hFile_In); VirtualFree(lpFileImage, 0, MEM_RELEASE);return false; }
3.2目标文件预处理
// 3. 获取PE文件信息 objProcPE.GetPeInfo(lpFileImage, dwFileSize, &stcPeInfo); // 4. 获取目标文件代码段的起始结束信息 // 读取第一个区段的相关信息,并将其加密(默认第一个区段为代码段) PBYTE lpStart = (PBYTE)(stcPeInfo.pSectionHeader->PointerToRawData + (DWORD)lpFileImage); PBYTE lpEnd = (PBYTE)((DWORD)lpStart + stcPeInfo.pSectionHeader->SizeOfRawData); PBYTE lpStartVA = (PBYTE)(stcPeInfo.pSectionHeader->VirtualAddress + stcPeInfo.dwImageBase); PBYTE lpEndVA = (PBYTE)((DWORD)lpStartVA + stcPeInfo.pSectionHeader->SizeOfRawData); // 5. 对文件进行预处理 Pretreatment(lpStart, lpEnd, stcPeInfo); void Pretreatment(PBYTE lpCodeStart, PBYTE lpCodeEnd, PE_INFO stcPeInfo) { // 1. 加密指定区域while (lpCodeStart < lpCodeEnd) { *lpCodeStart ^= 0x15; lpCodeStart++; } // 2. 给第一个区段附加上可写属性 PDWORD pChara = &(stcPeInfo.pSectionHeader->Characteristics); *pChara = *pChara | IMAGE_SCN_MEM_WRITE; }
3.3植入Stub
3.3.1区块增加
PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(m_pNt_Header);// 1. 获取基本信息DWORD dwDosSize = m_pDos_Header->e_lfanew;DWORD dwPeSize = sizeof(IMAGE_NT_HEADERS32);DWORD dwStnSize = m_pNt_Header->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER);DWORD dwHeadSize = dwDosSize+dwPeSize+dwStnSize;
我们查询一下关于PE文件的SectionTable的结构,单个SectionHeader的结构如下
#define IMAGE_SIZEOF_SHORT_NAME 8typedef struct _IMAGE_SECTION_HEADER{0X00 BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //节(段)的名字.text/.data/.rdata/等//由于长度固定8字节,所以可以没有结束符0X08 union{ DWORD PhysicalAddress; //物理地址 DWORD VirtualSize; //虚拟大小 }Misc;0X0C DWORD VirtualAddress; //块的RVA,相对虚拟地址0X10 DWORD SizeOfRawData; //该节在文件对齐后的尺寸大小(FileAlignment的整数倍)0X14 DWORD PointerToRawData; //节区在文件中的偏移量//0X18 DWORD PointerToRelocations; //重定位偏移(obj中使用)//0X1C DWORD PointerToLinenumbers; //行号表偏移(调试用)//0X20 WORD NumberOfRelocations; //重定位项目数(obj中使用)//0X22 WORD NumberOfLinenumbers; //行号表中行号的数目0X24 DWORD Characteristics; //节属性(按bit位设置属性)} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;#define IMAGE_SIZEOF_SECTION_HEADER 40
// 2.4 组装一个新的区段头 IMAGE_SECTION_HEADER stcNewSect = {0}; CopyMemory(stcNewSect.Name, szVarName, 7); // 区段名称 stcNewSect.Misc.VirtualSize = dwVirtualSize; // 虚拟大小 stcNewSect.VirtualAddress = dwVStart; // 虚拟地址 stcNewSect.SizeOfRawData = dwSizeOfRawData; // 文件大小 stcNewSect.PointerToRawData = dwFStart; // 文件地址 stcNewSect.Characteristics = dwChara; // 区段属性 // 2.5 写入指定位置 CopyMemory( (PVOID)((DWORD)m_dwFileDataAddr+dwHeadSize), &stcNewSect, sizeof(IMAGE_SECTION_HEADER) ); // 3. 修改区段数目字段NumberOfSections m_pNt_Header->FileHeader.NumberOfSections++;
// 4. 修改PE文件的景象尺寸字段SizeOfImage m_pNt_Header->OptionalHeader.SizeOfImage += dwVirtualSize;// 5. 返回新区段的详细信息、大小,以及可直接访问的地址 CopyMemory(pNewSection, &stcNewSect, sizeof(IMAGE_SECTION_HEADER)); *lpSize = dwSizeOfRawData;return (PVOID)(m_dwFileDataAddr+dwFStart); }
3.3.2修复重定位
// 1. 获取映像基址与代码段指针DWORD dwImageBase;PVOID lpCode;dwImageBase = m_pNt_Header->OptionalHeader.ImageBase;lpCode = (PVOID)( (DWORD)m_dwFileDataAddr + RVAToOffset(m_pNt_Header->OptionalHeader.BaseOfCode) );
// 2. 获取重定位表在内存中的地址 PIMAGE_DATA_DIRECTORY pDataDir; PIMAGE_BASE_RELOCATION pReloc; pDataDir = m_pNt_Header->OptionalHeader.DataDirectory+IMAGE_DIRECTORY_ENTRY_BASERELOC; pReloc = (PIMAGE_BASE_RELOCATION)((DWORD)m_dwFileDataAddr + RVAToOffset(pDataDir->VirtualAddress)); // 3. 遍历重定位表,并对目标代码进行重定位while ( pReloc->SizeOfBlock && pReloc->SizeOfBlock < 0x100000 ) {// 3.1 取得重定位项TypeOffset与其数量 PWORD pTypeOffset = (PWORD)((DWORD)pReloc+sizeof(IMAGE_BASE_RELOCATION)); DWORD dwCount = (pReloc->SizeOfBlock-sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD); // 3.2 循环检查重定位项for ( DWORD i=0; i {if ( !*pTypeOffset ) continue; // 3.2.1 获取此重定位项指向的指针 DWORD dwPointToRVA = (*pTypeOffset&0x0FFF)+pReloc->VirtualAddress; PDWORD pPtr = (PDWORD)(RVAToOffset(dwPointToRVA)+(DWORD)m_dwFileDataAddr);// 3.2.2 计算重定位增量值 DWORD dwIncrement = dwLoadImageAddr - dwImageBase;// 3.2.3 修复需重定位的地址数据 *((PDWORD)pPtr) += dwIncrement; pTypeOffset++; } // 3.3 指向下一个重定位块,开始另一次循环 pReloc = (PIMAGE_BASE_RELOCATION)((DWORD)pReloc + pReloc->SizeOfBlock); }
由此我们成功修复了重定位表
3.4 CProcessingPE
CProcessingPE::CProcessingPE(void) { ZeroMemory(&m_stcPeInfo, sizeof(PE_INFO)); } CProcessingPE::~CProcessingPE(void) { }/********************************************************************** 相对虚拟地址(RVA)转文件偏移(Offset)* 此函数负责将传入的RVA转换为Offset。** 注意:此转换函数并未考虑到所有细节,但是在绝大多数情况可以正常运转。** 参数:* ULONG uRvaAddr:RVA地址值* * 返回值:* DWORD:成功返回Offset,失败则返回0*********************************************************************/ DWORD CProcessingPE::RVAToOffset(ULONG uRvaAddr) { PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(m_pNt_Header); for (DWORD i=0; iFileHeader.NumberOfSections; i++) { if((pSectionHeader[i].VirtualAddress <= uRvaAddr) && (pSectionHeader[i].VirtualAddress + pSectionHeader[i].SizeOfRawData > uRvaAddr)) { return (pSectionHeader[i].PointerToRawData + (uRvaAddr - pSectionHeader[i].VirtualAddress)); } } return 0; }/********************************************************************** 文件偏移(Offset)转相对虚拟地址(RVA)* 此函数负责将传入的Offset转换为RVA。** 注意:此转换函数并未考虑到所有细节,但是在绝大多数情况可以正常运转。** 参数:* ULONG uOffsetAddr:Offset地址值* * 返回值:* DWORD:成功返回RVA地址,失败则返回0*********************************************************************/ DWORD CProcessingPE::OffsetToRVA(ULONG uOffsetAddr) { PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(m_pNt_Header); for (DWORD i=0; iFileHeader.NumberOfSections; i++) { if((pSectionHeader[i].PointerToRawData <= uOffsetAddr) && (pSectionHeader[i].PointerToRawData + pSectionHeader[i].SizeOfRawData > uOffsetAddr)) { return (pSectionHeader[i].VirtualAddress + (uOffsetAddr - pSectionHeader[i].PointerToRawData)); } } return 0; }/********************************************************************** 获取PE文件信息* 此函数负责获取目标文件的关键PE信息。** 参数:* LPVOID lpImageData:目标文件所在缓存区的指针* DWORD dwImageSize:目标文件的大小* PPE_INFO pPeInfo :[OUT]用于传出目标文件的关键PE信息* * 返回值:* BOOL:成功返回true,失败则返回false*********************************************************************/ BOOL CProcessingPE::GetPeInfo(LPVOID lpImageData, DWORD dwImageSize, PPE_INFO pPeInfo) { // 1、判断映像指针是否有效 if ( m_stcPeInfo.dwOEP ) { CopyMemory(pPeInfo, &m_stcPeInfo, sizeof(PE_INFO)); return true; } else { if ( !lpImageData ) return false; m_dwFileDataAddr = (DWORD)lpImageData; m_dwFileDataSize = dwImageSize; } // 2. 获取基本信息 // 2.1 获取DOS头、NT头 m_pDos_Header = (PIMAGE_DOS_HEADER)lpImageData; m_pNt_Header = (PIMAGE_NT_HEADERS)((DWORD)lpImageData+m_pDos_Header->e_lfanew); // 2.2 获取OEP m_stcPeInfo.dwOEP = m_pNt_Header->OptionalHeader.AddressOfEntryPoint; // 2.3 获取映像基址 m_stcPeInfo.dwImageBase = m_pNt_Header->OptionalHeader.ImageBase; // 2.4 获取关键数据目录表的内容 PIMAGE_DATA_DIRECTORY lpDataDir = m_pNt_Header->OptionalHeader.DataDirectory; m_stcPeInfo.pDataDir = lpDataDir; CopyMemory(&m_stcPeInfo.stcExport, lpDataDir+IMAGE_DIRECTORY_ENTRY_EXPORT, sizeof(IMAGE_DATA_DIRECTORY)); // 2.5 获取区段表与其他详细信息 m_stcPeInfo.pSectionHeader = IMAGE_FIRST_SECTION(m_pNt_Header); // 3. 检查PE文件是否有效 if ( (m_pDos_Header->e_magic!=IMAGE_DOS_SIGNATURE) || (m_pNt_Header->Signature!=IMAGE_NT_SIGNATURE) ) { // 这不是一个有效的PE文件 return false; } // 4. 传出处理结果 CopyMemory(pPeInfo, &m_stcPeInfo, sizeof(PE_INFO)); return true; }/********************************************************************** 修复重定位项* 此函数负责修复映像的重定位项,此函数依赖于RVAToOffset函数。* * 注意:* 1. dwLoadImageAddr指的并非是其本身ImageBase的值,而是其被加载后的预* 计模块基址。* 2. 此重定位函数并未考虑到修复类型问题,如果要提高兼容性,应该分别对* 三种重定位类型进行区别对待。** 参数:* DWORD dwLoadImageAddr:此映像被加载后的预计模块基址* * 返回值:无*********************************************************************/ void CProcessingPE::FixReloc(DWORD dwLoadImageAddr) { // 1. 获取映像基址与代码段指针 DWORD dwImageBase; PVOID lpCode; dwImageBase = m_pNt_Header->OptionalHeader.ImageBase; lpCode = (PVOID)( (DWORD)m_dwFileDataAddr + RVAToOffset(m_pNt_Header->OptionalHeader.BaseOfCode) ); // 2. 获取重定位表在内存中的地址 PIMAGE_DATA_DIRECTORY pDataDir; PIMAGE_BASE_RELOCATION pReloc; pDataDir = m_pNt_Header->OptionalHeader.DataDirectory+IMAGE_DIRECTORY_ENTRY_BASERELOC; pReloc = (PIMAGE_BASE_RELOCATION)((DWORD)m_dwFileDataAddr + RVAToOffset(pDataDir->VirtualAddress)); // 3. 遍历重定位表,并对目标代码进行重定位 while ( pReloc->SizeOfBlock && pReloc->SizeOfBlock < 0x100000 ) { // 3.1 取得重定位项TypeOffset与其数量 PWORD pTypeOffset = (PWORD)((DWORD)pReloc+sizeof(IMAGE_BASE_RELOCATION)); DWORD dwCount = (pReloc->SizeOfBlock-sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD); // 3.2 循环检查重定位项 for ( DWORD i=0; iVirtualAddress; PDWORD pPtr = (PDWORD)(RVAToOffset(dwPointToRVA)+(DWORD)m_dwFileDataAddr); // 3.2.2 计算重定位增量值 DWORD dwIncrement = dwLoadImageAddr - dwImageBase; // 3.2.3 修复需重定位的地址数据 *((PDWORD)pPtr) += dwIncrement; pTypeOffset++; } // 3.3 指向下一个重定位块,开始另一次循环 pReloc = (PIMAGE_BASE_RELOCATION)((DWORD)pReloc + pReloc->SizeOfBlock); } }/********************************************************************** 获取PE文件信息* 此函数负责获取目标文件的关键PE信息。** 参数:* LPVOID lpImageData:目标文件所在缓存区的指针* DWORD dwImageSize:目标文件的大小* PPE_INFO pPeInfo :[OUT]用于传出目标文件的关键PE信息* * 返回值:* BOOL:成功返回true,失败则返回false*********************************************************************/ PVOID CProcessingPE::GetExpVarAddr(LPCTSTR strVarName) { // 1、获取导出表地址,并将参数strVarName转为ASCII形式,方便对比查找 CHAR szVarName[MAX_PATH] = {0}; PIMAGE_EXPORT_DIRECTORY lpExport = (PIMAGE_EXPORT_DIRECTORY)(m_dwFileDataAddr + RVAToOffset(m_stcPeInfo.stcExport.VirtualAddress)); WideCharToMultiByte(CP_ACP, NULL, strVarName, -1, szVarName, _countof(szVarName), NULL, FALSE); // 2、循环读取导出表输出项的输出函数,并依次与szVarName做比对,如果相同,则取出相对应的函数地址 for (DWORD i=0; iNumberOfNames; i++) { PDWORD pNameAddr = (PDWORD)(m_dwFileDataAddr+RVAToOffset(lpExport->AddressOfNames+i)); PCHAR strTempName = (PCHAR)(m_dwFileDataAddr + RVAToOffset(*pNameAddr)); if ( !strcmp(szVarName, strTempName) ) { PDWORD pFunAddr = (PDWORD)(m_dwFileDataAddr+RVAToOffset(lpExport->AddressOfFunctions+i)); return (PVOID)(m_dwFileDataAddr + RVAToOffset(*pFunAddr)); } } return 0; }/********************************************************************** 添加区段函数* 此函数负责在目标文件中添加一个自定义的区段。** 注:* 此函数并未考虑到目标函数存在附加数据等细节问题。** 参数:* LPCTSTR strName :新区段的名称* DWORD dwSize :新区段的最小体积* DWORD dwChara :新区段的属性* PIMAGE_SECTION_HEADER pNewSection:[OUT]新区段的段结构指针* PDWORD lpSize :[OUT]新区段的最终大小* * 返回值:* PVOID:成功返回指向新区段现在所在内存的指针*********************************************************************/ PVOID CProcessingPE::AddSection(LPCTSTR strName, DWORD dwSize, DWORD dwChara, PIMAGE_SECTION_HEADER pNewSection, PDWORD lpSize) { PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(m_pNt_Header); // 1. 获取基本信息 DWORD dwDosSize = m_pDos_Header->e_lfanew; DWORD dwPeSize = sizeof(IMAGE_NT_HEADERS32); DWORD dwStnSize = m_pNt_Header->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER); DWORD dwHeadSize = dwDosSize+dwPeSize+dwStnSize; // 2. 在区段表中加入新区段的信息 // 2.1 获取基本信息 CHAR szVarName[7] = {0}; DWORD dwFileAlign = m_pNt_Header->OptionalHeader.FileAlignment; // 文件粒度 DWORD dwSectAlign = m_pNt_Header->OptionalHeader.SectionAlignment; // 区段粒度 WORD dwNumOfsect = m_pNt_Header->FileHeader.NumberOfSections; // 区段数目 // 2.2 获取最后一个区段的信息 IMAGE_SECTION_HEADER stcLastSect = {0}; CopyMemory(&stcLastSect, &pSectionHeader[dwNumOfsect-1], sizeof(IMAGE_SECTION_HEADER)); // 2.3 根据区段粒度计算相应地址信息 DWORD dwVStart = 0; // 虚拟地址起始位置 DWORD dwFStart = stcLastSect.SizeOfRawData + stcLastSect.PointerToRawData; // 文件地址起始位置 if ( stcLastSect.Misc.VirtualSize%dwSectAlign ) dwVStart = (stcLastSect.Misc.VirtualSize / dwSectAlign+1) * dwSectAlign + stcLastSect.VirtualAddress; else dwVStart = (stcLastSect.Misc.VirtualSize / dwSectAlign ) * dwSectAlign + stcLastSect.VirtualAddress; DWORD dwVirtualSize = 0; // 区段虚拟大小 DWORD dwSizeOfRawData = 0; // 区段文件大小 if ( dwSize%dwSectAlign) dwVirtualSize = (dwSize / dwSectAlign+1) * dwSectAlign; else dwVirtualSize = (dwSize / dwSectAlign ) * dwSectAlign; if ( dwSize%dwFileAlign ) dwSizeOfRawData = (dwSize / dwFileAlign+1) * dwFileAlign; else dwSizeOfRawData = (dwSize / dwFileAlign ) * dwFileAlign; WideCharToMultiByte(CP_ACP, NULL, strName, -1, szVarName, _countof(szVarName), NULL, FALSE); // 2.4 组装一个新的区段头 IMAGE_SECTION_HEADER stcNewSect = {0}; CopyMemory(stcNewSect.Name, szVarName, 7); // 区段名称 stcNewSect.Misc.VirtualSize = dwVirtualSize; // 虚拟大小 stcNewSect.VirtualAddress = dwVStart; // 虚拟地址 stcNewSect.SizeOfRawData = dwSizeOfRawData; // 文件大小 stcNewSect.PointerToRawData = dwFStart; // 文件地址 stcNewSect.Characteristics = dwChara; // 区段属性 // 2.5 写入指定位置 CopyMemory( (PVOID)((DWORD)m_dwFileDataAddr+dwHeadSize), &stcNewSect, sizeof(IMAGE_SECTION_HEADER) ); // 3. 修改区段数目字段NumberOfSections m_pNt_Header->FileHeader.NumberOfSections++; // 4. 修改PE文件的景象尺寸字段SizeOfImage m_pNt_Header->OptionalHeader.SizeOfImage += dwVirtualSize; // 5. 返回新区段的详细信息、大小,以及可直接访问的地址 CopyMemory(pNewSection, &stcNewSect, sizeof(IMAGE_SECTION_HEADER)); *lpSize = dwSizeOfRawData; return (PVOID)(m_dwFileDataAddr+dwFStart); }/********************************************************************** 修改目标文件OEP* 此函数负责修改目标文件OEP。** 参数:* DWORD dwOEP:新OEP* * 返回值:无*********************************************************************/ void CProcessingPE::SetOEP(DWORD dwOEP) { m_pNt_Header->OptionalHeader.AddressOfEntryPoint = dwOEP; }
总结
壳的编写看似简单,实际有不少难点。本文中没有涉及IAT表的加密以及一系列反调试,混淆手段,只是作为一个壳的原型方便读者了解壳编写的流程以及其中的难点,为下一步深入学习打下基础。
本人纯属萌新,本文如有错漏,希望各位大佬不吝指正。
本文参考的代码来自于《黑客免杀攻防》,如有需要源码可以自行前往华章图 书的官网下载。
如果您有任何问题,请跟我们联系!
联系我们