前言
项目总共分两部分完成:
Packer
: 负责压缩、注入ShellCode
,生成新的可执行文件。Code
: 负责编写ShellCode
,功能是解压缩、修复IAT、修复重定位表、跳转到OEP。
因为涉及到了ShellCode
的编写,所以我们为了方便,指定我们的加壳程序只能加壳x86
的可执行程序。
Packer
功能大体设计的思路如下:
- 读取需要加壳的可执行文件
- 压缩可执行文件的数据
- 重新生成新的可执行文件
- 注入
ShellCode
到新的可执行文件
Code
功能大体设计的思路如下:
- 通过
PEB
获取kernel32.dll
的基址,然后获取LoadLibraryA
函数。 - 编写我们自己的
GetProcAddress
函数,用于从模块中获取导出函数的地址。 - 解压缩PE数据。
- 映射节表数据到内存。
- 修复IAT。
- 修复重定位表。
- 拷贝PE头部到内存。
- 修复节表内存属性。
- 跳转到OEP。
新的可执行程序的节表我们设计如下:
.old
: 存放[压缩信息+原始可执行文件的数据].pack
: 存放ShellCode
的代码
新的可执行文件不包含导入表,我们可以通过获取PEB
的Ldr
链表找到kernel32.dll
,然后通过GetProcAddress
获取LoadLibraryA
、GetProcAddress
、VirtualAlloc
等函数。
Packer
1. 读取需要加壳的可执行文件
int CPacker::ParsePE(){ MapAndLoad(m_strPath.c_str(), NULL, &m_LoadedImage, FALSE, TRUE); return 0;}
MapAndLoad
函数是DbgHelp
提供的函数,用于加载一个可执行文件到内存中,我们可以通过m_LoadedImage
获取到PE
的信息。
2. 压缩可执行文件的数据
int CPacker::CompressData(){ SIZE_T CompressedBufferSize; PBYTE InputBuffer = NULL; HANDLE InputFile = INVALID_HANDLE_VALUE; HANDLE CompressedFile = INVALID_HANDLE_VALUE; BOOL DeleteTargetFile = TRUE; BOOL Success; //遍历节表 获取数据 InputBuffer = (PBYTE)m_LoadedImage.MappedAddress + m_LoadedImage.Sections[0].PointerToRawData; DWORD InputFileSize = 0; for (ULONG i = 0; i < m_LoadedImage.NumberOfSections; i++) { InputFileSize += m_LoadedImage.Sections[i].SizeOfRawData; } m_DataSize = InputFileSize;
Success = CreateCompressor( COMPRESS_ALGORITHM_LZMS, NULL, &m_Compressor); if (!Success) { printf("Cannot create a compressor %d.\n", GetLastError()); goto done; } //查询压缩后的大小 Success = Compress( m_Compressor, // Compressor Handle InputBuffer, // Input buffer, Uncompressed data InputFileSize, // Uncompressed data size NULL, // Compressed Buffer 0, // Compressed Buffer size &CompressedBufferSize); // Compressed Data size //申请缓冲区 if (!Success) { DWORD ErrorCode = GetLastError(); if (ErrorCode != ERROR_INSUFFICIENT_BUFFER) { printf("Cannot compress data: %d.\n", ErrorCode); goto done; }
m_CompressedBuffer = (PBYTE)malloc(CompressedBufferSize); if (!m_CompressedBuffer) { printf("Cannot allocate memory for compressed buffer.\n"); goto done; } } //压缩数据 Success = Compress( m_Compressor, // Compressor Handle InputBuffer, // Input buffer, Uncompressed data InputFileSize, // Uncompressed data size m_CompressedBuffer, // Compressed Buffer CompressedBufferSize, // Compressed Buffer size &m_CompressedDataSize); // Compressed Data size if (!Success) { printf("Cannot compress data: %d\n", GetLastError()); goto done; } printf("Input file size: %d; Compressed Size: %d\n", InputFileSize, m_CompressedDataSize); DeleteTargetFile = FALSE; //查询压缩后的大小 InputBuffer = m_LoadedImage.MappedAddress; InputFileSize = m_LoadedImage.FileHeader->OptionalHeader.SizeOfHeaders; m_PeHeaderDataSize = InputFileSize; Success = Compress( m_Compressor, // Compressor Handle InputBuffer, // Input buffer, Uncompressed data InputFileSize, // Uncompressed data size NULL, // Compressed Buffer 0, // Compressed Buffer size &m_PeHeaderComDataSize); // Compressed Data size //申请缓冲区 if (!Success) { DWORD ErrorCode = GetLastError(); if (ErrorCode != ERROR_INSUFFICIENT_BUFFER) { printf("Cannot compress data: %d.\n", ErrorCode); goto done; } m_PeHeaderBuffer = (PBYTE)malloc(m_PeHeaderComDataSize); if (!m_PeHeaderBuffer) { printf("Cannot allocate memory for compressed buffer.\n"); goto done; } } //压缩数据 Success = Compress( m_Compressor, // Compressor Handle InputBuffer, // Input buffer, Uncompressed data InputFileSize, // Uncompressed data size m_PeHeaderBuffer, // Compressed Buffer m_PeHeaderDataSize, // Compressed Buffer size &m_PeHeaderComDataSize); // Compressed Data size if (!Success) { printf("Cannot compress data: %d\n", GetLastError()); goto done; } printf("Input file size: %d; Compressed Size: %d\n", InputFileSize, m_PeHeaderDataSize); DeleteTargetFile = FALSE; return 0;done: if (m_Compressor != NULL) { CloseCompressor(m_Compressor); }
if (m_CompressedBuffer) { free(m_CompressedBuffer); }
return -1;}
我们分两部分压缩数据,第一部分是PE
的头部,第二部分是PE
的数据部分。注意我们需要保存压缩后的大小,以便解压缩时使用。
3. 重新生成新的可执行文件
int CPacker::ReBuildPe(){ //拷贝Dos头 m_PackDosHeader = *(IMAGE_DOS_HEADER*)m_LoadedImage.MappedAddress; m_PackDosHeader.e_lfanew = sizeof(IMAGE_DOS_HEADER); //拷贝NT头 m_PackNtHeader = *m_LoadedImage.FileHeader;
//处理文件头 m_PackNtHeader.FileHeader.NumberOfSections = 2;
IMAGE_OPTIONAL_HEADER* pOptionHeader = &m_PackNtHeader.OptionalHeader;
//删除所有数据目录 for (DWORD i = 0; i < pOptionHeader->NumberOfRvaAndSizes; i++) { pOptionHeader->DataDirectory[i].VirtualAddress = 0; pOptionHeader->DataDirectory[i].Size = 0; }
//处理压缩节 ZeroMemory(&m_PackSectionHeader[0], sizeof(m_PackSectionHeader[0])); DWORD dwSize = pOptionHeader->SizeOfImage - Alignment(pOptionHeader->SizeOfHeaders, pOptionHeader->SectionAlignment); strcpy((char*)m_PackSectionHeader[0].Name, ".old"); m_PackSectionHeader[0].Misc.VirtualSize = dwSize; m_PackSectionHeader[0].VirtualAddress = m_LoadedImage.Sections[0].VirtualAddress; m_PackSectionHeader[0].SizeOfRawData = 0; m_PackSectionHeader[0].PointerToRawData = 0; m_PackSectionHeader[0].Characteristics = IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE;
//处理代码节 ZeroMemory(&m_PackSectionHeader[1], sizeof(m_PackSectionHeader[1])); strcpy((char*)m_PackSectionHeader[1].Name, ".pack"); dwSize = Alignment(sizeof(PackInfo) + m_PeHeaderComDataSize + m_CompressedDataSize + sizeof(hexData), pOptionHeader->FileAlignment); m_PackSectionHeader[1].SizeOfRawData = dwSize; m_PackSectionHeader[1].PointerToRawData = pOptionHeader->SizeOfHeaders; m_PackSectionHeader[1].Misc.VirtualSize = Alignment(m_PackSectionHeader[1].SizeOfRawData, pOptionHeader->SectionAlignment); m_PackSectionHeader[1].VirtualAddress = m_PackSectionHeader[0].VirtualAddress + m_PackSectionHeader[0].Misc.VirtualSize; m_PackSectionHeader[1].Characteristics = IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE;
//处理选项头 pOptionHeader->SizeOfImage = m_PackSectionHeader[0].Misc.VirtualSize + m_PackSectionHeader[1].Misc.VirtualSize + Alignment(pOptionHeader->SizeOfHeaders, pOptionHeader->SectionAlignment); pOptionHeader->AddressOfEntryPoint = sizeof(PackInfo) + m_PeHeaderComDataSize + m_CompressedDataSize; pOptionHeader->AddressOfEntryPoint += m_PackSectionHeader[1].VirtualAddress; pOptionHeader->AddressOfEntryPoint += 0x380;
return 0;}
我们重新生成新的可执行文件,主要是修改NT
头部的一些信息,以及添加新的节表。
4. 注入ShellCode
到新的可执行文件
int CPacker::CreateNewPe(){ FILE *fp = fopen(m_strDstPath.c_str(), "wb"); if (fp != NULL) { fwrite(&m_PackDosHeader, 1, sizeof(m_PackDosHeader), fp); fwrite(&m_PackNtHeader, 1, sizeof(m_PackNtHeader), fp); fwrite(m_PackSectionHeader, 1, sizeof(m_PackSectionHeader), fp); fseek(fp, m_PackNtHeader.OptionalHeader.SizeOfHeaders, SEEK_SET); PackInfo Info = { 0 }; Info.dwHeaderSize = m_PeHeaderDataSize; Info.dwHeaderComSize = m_PeHeaderComDataSize; Info.dwDataSize = m_DataSize; Info.dwDataComSize = m_CompressedDataSize;
fwrite(&Info, 1, sizeof(Info), fp); fwrite(m_PeHeaderBuffer, 1, m_PeHeaderComDataSize, fp); fwrite(m_CompressedBuffer, 1, m_CompressedDataSize, fp); fwrite(hexData, 1, sizeof(hexData), fp); DWORD dwBytes = ftell(fp); dwBytes = Alignment(dwBytes, m_PackNtHeader.OptionalHeader.FileAlignment) - dwBytes; char ch = 0; for (DWORD i = 0; i < dwBytes; i++) { fwrite(&ch, 1, sizeof(ch), fp); } fclose(fp); } return 0;}
hexData
是我们的ShellCode
,我们目前还没有这个变量,接下来我们编写ShellCode
。
Code
1. 通过PEB
获取kernel32.dll
的基址,然后获取LoadLibraryA
函数。
__declspec(naked) PVOID MyGetKernel32Base() { __asm { mov eax, fs: [30h] ; _PEB mov eax, [eax + 0ch]; _PEB_LDR_DATA mov eax, [eax + 0ch]; _LDR_DATA_TABLE_ENTRY mov eax, [eax]; ntdll mov eax, [eax]; kernel32 mov eax, [eax + 18h]; _LDR_DATA_TABLE_ENTRY.DllBase ret }}
2. 编写我们自己的GetProcAddress
函数,用于从模块中获取导出函数的地址。
FARPROC MyGetProcAddress(HMODULE hModule, PackEnv *pEnv, LPCSTR lpProcName) { IMAGE_DOS_HEADER* pDosHeader = (IMAGE_DOS_HEADER*)hModule; //if (pDosHeader->e_magic != 0x5A4D) { // return NULL; //}
IMAGE_NT_HEADERS* pNtHeader = (IMAGE_NT_HEADERS*)((char*)hModule + pDosHeader->e_lfanew); /* if (pNtHeader->Signature != 0x00004550) { return NULL; }*/ DWORD dwVirtualAddress = pNtHeader->OptionalHeader.DataDirectory[0].VirtualAddress; if (dwVirtualAddress == 0) { return NULL; } DWORD dwSize = pNtHeader->OptionalHeader.DataDirectory[0].Size; if (dwSize == 0) { return NULL; } IMAGE_EXPORT_DIRECTORY* pExportTable = (IMAGE_EXPORT_DIRECTORY*)((char*)hModule + dwVirtualAddress); DWORD* pAddressOfFunctions = (DWORD*)((char*)hModule + pExportTable->AddressOfFunctions); if (pAddressOfFunctions == NULL) { return NULL; } WORD* pAddressOfNameOrdinals = (WORD*)((char*)hModule + pExportTable->AddressOfNameOrdinals); DWORD* pAddressOfNames = (DWORD*)((char*)hModule + pExportTable->AddressOfNames);
//名称 if (HIWORD(lpProcName) != 0) { //遍历名称表 for (DWORD i = 0; i < pExportTable->NumberOfNames; i++) { LPCSTR pFuncName = (char*)hModule + pAddressOfNames[i]; if (my_strcmp(pFuncName, lpProcName) == 0) { DWORD dwRVA = pAddressOfFunctions[pAddressOfNameOrdinals[i]]; if (dwRVA == 0) return NULL;
//函数转发 if (dwRVA >= dwVirtualAddress && dwRVA <= dwVirtualAddress + dwSize) { LPCSTR lpString = (LPCSTR)hModule + dwRVA; char szLibName[MAXBYTE]; MyRtlZeroMemory(szLibName, sizeof(szLibName)); char szFuncName[MAXBYTE]; MyRtlZeroMemory(szFuncName, sizeof(szFuncName)); __asm nop int nLen = my_strlen(lpString); int i; for (i = 0; i < nLen; i++) { if (lpString[i] == '.') { break; } szLibName[i] = lpString[i]; } i++; //序号 if (lpString[i] == '#') { __asm nop DWORD dwOrder = my_strtoul(lpString + i + 1, NULL, 10); HMODULE hDll = pEnv->LoadLibraryA(szLibName); return MyGetProcAddress(hDll, pEnv, (LPCSTR)dwOrder); } else { HMODULE hDll = pEnv->LoadLibraryA(szLibName); my_strcpy_s(szFuncName, sizeof(szFuncName), lpString + i); return MyGetProcAddress(hDll, pEnv, szFuncName); } } else return (FARPROC)((LPCSTR)hModule + dwRVA); } } } //序号 else { WORD dwOrder = LOWORD(lpProcName); DWORD nIndex = dwOrder - pExportTable->Base; if (nIndex < pExportTable->NumberOfFunctions) { DWORD dwRVA = pAddressOfFunctions[nIndex]; if (dwRVA == 0) return NULL; return (FARPROC)((char*)hModule + dwRVA); } } return NULL;}
3. 解压缩PE数据。
//获取解压信息struct PackInfo *pPackInfo;char* lpImageBase = (char*)MyGetImageBase();IMAGE_DOS_HEADER *pDosHeader = (IMAGE_DOS_HEADER*)lpImageBase;IMAGE_NT_HEADERS* pNtHeader = (IMAGE_NT_HEADERS*)(lpImageBase + pDosHeader->e_lfanew);IMAGE_FILE_HEADER* pFileHeader = &pNtHeader->FileHeader;IMAGE_OPTIONAL_HEADER* pOptionHeader = &pNtHeader->OptionalHeader;IMAGE_SECTION_HEADER* pSections = (IMAGE_SECTION_HEADER*)(pOptionHeader + 1);
pPackInfo = (PackInfo*)(lpImageBase + pSections[1].VirtualAddress);PBYTE pPeHeaderBuffer = (PBYTE)(pPackInfo + 1);PBYTE pDataBuffer = (PBYTE)(pPeHeaderBuffer + pPackInfo->dwHeaderComSize);
PBYTE lpBuffer = (PBYTE)Env.VirtualAlloc(NULL, pPackInfo->dwHeaderSize + pPackInfo->dwDataSize, MEM_COMMIT, PAGE_READWRITE);if (lpBuffer == NULL) return;
DECOMPRESSOR_HANDLE Decompressor = NULL;Env.CreateDecompressor( COMPRESS_ALGORITHM_LZMS, NULL, &Decompressor);
//解压缩PE头DWORD dwSize = 0;Env.Decompress( Decompressor, pPeHeaderBuffer, pPackInfo->dwHeaderComSize, lpBuffer, pPackInfo->dwHeaderSize, &dwSize);
//解压缩数据Env.Decompress( Decompressor, pDataBuffer, pPackInfo->dwDataComSize, lpBuffer + pPackInfo->dwHeaderSize, pPackInfo->dwDataSize, &dwSize);
Env.CloseCompressor(Decompressor);
4. 映射节表数据到内存。
我们解压缩的时候已经将数据映射到内存中了。
5. 修复IAT。
//填IATPBYTE lpMapAddress = lpBuffer;pDosHeader = (IMAGE_DOS_HEADER*)lpMapAddress;/* if (pDosHeader->e_magic != 0x5A4D) { goto SAFE_EXIT; }*/
pNtHeader = (IMAGE_NT_HEADERS*)(lpMapAddress + pDosHeader->e_lfanew);/* if (pNtHeader->Signature != 0x00004550) { goto SAFE_EXIT; }*/
//遍历节表映射IMAGE_SECTION_HEADER* pSectionHeader = (IMAGE_SECTION_HEADER*)((char*)&pNtHeader->OptionalHeader + pNtHeader->FileHeader.SizeOfOptionalHeader);for (int i = 0; i < pNtHeader->FileHeader.NumberOfSections; i++) { MyCopyMemory(lpImageBase + pSectionHeader[i].VirtualAddress, lpMapAddress + pSectionHeader[i].PointerToRawData, pSectionHeader[i].SizeOfRawData);}
//映射头DWORD dwOld = 0;Env.VirtualProtect(lpImageBase, pNtHeader->OptionalHeader.SizeOfHeaders, PAGE_READWRITE, &dwOld);MyCopyMemory(lpImageBase, lpMapAddress, pNtHeader->OptionalHeader.SizeOfHeaders);Env.VirtualProtect(lpImageBase, pNtHeader->OptionalHeader.SizeOfHeaders, dwOld, &dwOld);
//填充IATif (pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress != 0) { IMAGE_IMPORT_DESCRIPTOR* pImportTable = NULL; pImportTable = (IMAGE_IMPORT_DESCRIPTOR*)(lpImageBase + pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
while (true) { if (pImportTable->Name == 0 && pImportTable->FirstThunk == 0 && pImportTable->OriginalFirstThunk == 0) { break; }
//printf("name:%s\n", lpImageBase + pImportTable->Name); HMODULE hMou = Env.LoadLibraryA(lpImageBase + pImportTable->Name); if (hMou == NULL) return;
//遍历名称表 if (pImportTable->FirstThunk == 0) return;
DWORD* lpImportAddressTable = (DWORD*)(lpImageBase + pImportTable->FirstThunk); DWORD* lpImportNameTable = lpImportAddressTable; if (pImportTable->OriginalFirstThunk != 0) { lpImportNameTable = (DWORD*)(lpImageBase + pImportTable->OriginalFirstThunk); }
for (int i = 0; lpImportNameTable[i]; i++) { //序号 LPCSTR lpFuncName = NULL; if (lpImportNameTable[i] & 0x80000000) { lpFuncName = (LPCSTR)(LOWORD(lpImportNameTable[i])); } else { lpFuncName = lpImageBase + lpImportNameTable[i] + 2; } LPVOID lpFuncAddress = MyGetProcAddress(hMou, &Env, lpFuncName); if (lpFuncAddress == NULL) return; lpImportAddressTable[i] = (DWORD)lpFuncAddress; }
//获取导入函数地址
//填入IAT pImportTable++; }}
6. 修复重定位表。
//遍历重定位表__asm nopif (pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress != 0) { IMAGE_BASE_RELOCATION* pRel = (IMAGE_BASE_RELOCATION*)(lpImageBase + pNtHeader->OptionalHeader.DataDirectory[5].VirtualAddress); while (true) { if (pRel->VirtualAddress == 0 && pRel->SizeOfBlock == 0) break;
DWORD dwCount = (pRel->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD); WORD* pTypeOffset = (WORD*)(pRel + 1); for (DWORD i = 0; i < dwCount; i++) { //获取代码地址 PVOID lpAdress = lpImageBase + pRel->VirtualAddress + (pTypeOffset[i] & 0xFFF);
switch (pTypeOffset[i] >> 12) { case IMAGE_REL_BASED_HIGHLOW: { *(DWORD*)lpAdress = (DWORD)(lpImageBase + *(DWORD*)lpAdress - pNtHeader->OptionalHeader.ImageBase); break; } } }
pRel = (IMAGE_BASE_RELOCATION*)((char*)pRel + pRel->SizeOfBlock); }}
7. 拷贝PE头部到内存。
8. 修复节表内存属性。
9. 跳转到OEP。
//OEPpDosHeader = (IMAGE_DOS_HEADER*)lpImageBase;pNtHeader = (IMAGE_NT_HEADERS*)(lpImageBase + pDosHeader->e_lfanew);pOptionHeader = &pNtHeader->OptionalHeader;((void (*)())(lpImageBase + pOptionHeader->AddressOfEntryPoint))();