Back

/ 11 min read

使用C++实现一个压缩壳

前言

项目总共分两部分完成:

  • Packer: 负责压缩、注入ShellCode,生成新的可执行文件。
  • Code: 负责编写ShellCode,功能是解压缩、修复IAT、修复重定位表、跳转到OEP。

因为涉及到了ShellCode的编写,所以我们为了方便,指定我们的加壳程序只能加壳x86的可执行程序。

Packer功能大体设计的思路如下:

  1. 读取需要加壳的可执行文件
  2. 压缩可执行文件的数据
  3. 重新生成新的可执行文件
  4. 注入ShellCode到新的可执行文件

Code功能大体设计的思路如下:

  1. 通过PEB获取kernel32.dll的基址,然后获取LoadLibraryA函数。
  2. 编写我们自己的GetProcAddress函数,用于从模块中获取导出函数的地址。
  3. 解压缩PE数据。
  4. 映射节表数据到内存。
  5. 修复IAT。
  6. 修复重定位表。
  7. 拷贝PE头部到内存。
  8. 修复节表内存属性。
  9. 跳转到OEP。

新的可执行程序的节表我们设计如下:

  1. .old: 存放[压缩信息+原始可执行文件的数据]
  2. .pack: 存放ShellCode的代码

新的可执行文件不包含导入表,我们可以通过获取PEBLdr链表找到kernel32.dll,然后通过GetProcAddress获取LoadLibraryAGetProcAddressVirtualAlloc等函数。

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。

//填IAT
PBYTE 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);
//填充IAT
if (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 nop
if (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。

//OEP
pDosHeader = (IMAGE_DOS_HEADER*)lpImageBase;
pNtHeader = (IMAGE_NT_HEADERS*)(lpImageBase + pDosHeader->e_lfanew);
pOptionHeader = &pNtHeader->OptionalHeader;
((void (*)())(lpImageBase + pOptionHeader->AddressOfEntryPoint))();