之前发了2篇文章,介绍PE基础和加壳器和壳代码编写。现在就调试一下被加壳的代码和加壳程序
被加壳代码
加壳前程序如下:
#include<Windows.h>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){
int i;
i = MessageBoxA(NULL, "do you?", "baby", MB_YESNO);
if (i == IDYES)
MessageBoxA(NULL, "Yes", "Yes", MB_OK);/*如果点击了“是”*/
else
MessageBoxA(NULL, "No", "No", MB_OK);/*否则*/
return 0;
}
IDA:
加壳后的代码:
start函数
sub_2D7440
就是解密函数
win10开了ASLR,所以ImageBase成了0x2D0000。而start函数在.cyxvc节里。
这里执行了jmp指令后跳到了.text节,0x1159(之前start函数地址)。
这里代码太多,但是WinMain RVA是0x1000,这里也一样。
这里执行壳代码后代码段已经恢复
加壳程序
需要加壳的PE程序信息
加壳程序的主函数
BOOL CPACK::Pack(CString strFilePath, BOOL bIsShowMesBox)
{
//1.读取PE文件信息并保存
CPE objPE;
if (objPE.InitPE(strFilePath) == FALSE)
return FALSE;
//2.加密代码段操作
DWORD dwXorSize = 0;
dwXorSize=objPE.XorCode(0x15);
//3.将必要的信息保存到Shell
HMODULE hShell = LoadLibrary(L"Shell.dll");
if (hShell == NULL)
{
MessageBox(NULL, _T("加载Shell.dll模块失败,请确保程序的完整性!"), _T("提示"), MB_OK);
//释放资源
delete[] objPE.m_pFileBuf;
return FALSE;
}
PSHELL_DATA pstcShellData = (PSHELL_DATA)GetProcAddress(hShell, "g_stcShellData");
pstcShellData->dwXorKey = 0x15;
pstcShellData->dwCodeBase = objPE.m_dwCodeBase;
pstcShellData->dwXorSize = dwXorSize;
pstcShellData->dwPEOEP = objPE.m_dwPEOEP;
pstcShellData->dwPEImageBase = objPE.m_dwImageBase;
pstcShellData->stcPERelocDir = objPE.m_PERelocDir;
pstcShellData->stcPEImportDir = objPE.m_PEImportDir;
pstcShellData->dwIATSectionBase = objPE.m_IATSectionBase;
pstcShellData->dwIATSectionSize = objPE.m_IATSectionSize;
pstcShellData->bIsShowMesBox = bIsShowMesBox;
//4.将Shell附加到PE文件
//4.1.读取Shell代码
MODULEINFO modinfo = { 0 };
GetModuleInformation(GetCurrentProcess(), hShell, &modinfo, sizeof(MODULEINFO));
PBYTE pShellBuf = new BYTE[modinfo.SizeOfImage];
memcpy_s(pShellBuf, modinfo.SizeOfImage, hShell, modinfo.SizeOfImage);
//4.2.设置Shell重定位信息
objPE.SetShellReloc(pShellBuf, (DWORD)hShell);
//4.3.修改被加壳程序的OEP,指向Shell
DWORD dwShellOEP = pstcShellData->dwStartFun - (DWORD)hShell;
objPE.SetNewOEP(dwShellOEP);
//4.4.合并PE文件和Shell的代码到新的缓冲区
LPBYTE pFinalBuf = NULL;
DWORD dwFinalBufSize = 0;
objPE.MergeBuf(objPE.m_pFileBuf, objPE.m_dwImageSize,
pShellBuf, modinfo.SizeOfImage,
pFinalBuf, dwFinalBufSize);
//5.保存文件(处理完成的缓冲区)
SaveFinalFile(pFinalBuf, dwFinalBufSize, strFilePath);
//6.释放资源
delete[] objPE.m_pFileBuf;
delete[] pShellBuf;
delete[] pFinalBuf;
objPE.InitValue();
return TRUE;
}
这里读取PE文件和加密就不细说了。
pstcShellData
的值如下
hshell
的值为0x7c700000
读取shell.dll中的信息
查看modinfo
的值
可以看到shell.dll的映像大小和shellTest一样,都是0x6000字节。
暂时不看重定向和修改IAT的函数。
这里
dwShellOEP
也就是shell中start
函数的RVA是0x1230。
MergeBuf
合并2个PE的信息
void CPE::MergeBuf(LPBYTE pFileBuf, DWORD pFileBufSize,
LPBYTE pShellBuf, DWORD pShellBufSize,
LPBYTE& pFinalBuf, DWORD& pFinalBufSize)
{
//获取最后一个区段的信息
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFileBuf;
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(pFileBuf + pDosHeader->e_lfanew);
PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pNtHeader);
PIMAGE_SECTION_HEADER pLastSection =
&pSectionHeader[pNtHeader->FileHeader.NumberOfSections - 1];
//1.修改区段数量
pNtHeader->FileHeader.NumberOfSections += 1;
//2.编辑区段表头结构体信息
PIMAGE_SECTION_HEADER AddSectionHeader =
&pSectionHeader[pNtHeader->FileHeader.NumberOfSections - 1];
memcpy_s(AddSectionHeader->Name, 8, ".cyxvc", 7);
//VOffset(1000对齐)
DWORD dwTemp = 0;
dwTemp = (pLastSection->Misc.VirtualSize / m_dwMemAlign) * m_dwMemAlign;
if (pLastSection->Misc.VirtualSize % m_dwMemAlign)
{
dwTemp += 0x1000;
}
AddSectionHeader->VirtualAddress = pLastSection->VirtualAddress + dwTemp;
//Vsize(实际添加的大小)
AddSectionHeader->Misc.VirtualSize = pShellBufSize;
//ROffset(旧文件的末尾)
AddSectionHeader->PointerToRawData = pFileBufSize;
//RSize(200对齐)
dwTemp = (pShellBufSize / m_dwFileAlign) * m_dwFileAlign;
if (pShellBufSize % m_dwFileAlign)
{
dwTemp += m_dwFileAlign;
}
AddSectionHeader->SizeOfRawData = dwTemp;
//标志
AddSectionHeader->Characteristics = 0XE0000040;
//3.修改PE头文件大小属性,增加文件大小
dwTemp = (pShellBufSize / m_dwMemAlign) * m_dwMemAlign;
if (pShellBufSize % m_dwMemAlign)
{
dwTemp += m_dwMemAlign;
}
pNtHeader->OptionalHeader.SizeOfImage += dwTemp;
//4.申请合并所需要的空间
pFinalBuf = new BYTE[pFileBufSize + dwTemp];
pFinalBufSize = pFileBufSize + dwTemp;
memset(pFinalBuf, 0, pFileBufSize + dwTemp);
memcpy_s(pFinalBuf, pFileBufSize, pFileBuf, pFileBufSize);
memcpy_s(pFinalBuf + pFileBufSize, dwTemp, pShellBuf, dwTemp);
}
先看看pSectionHeader
和pLastSection
的值
pSectionHeader
和节表第一个信息一致
pLastSection
为节表最后一项
之后就是对节表的新项AddSectionHeader
疯狂设置,设置好后
之后就是增大PE文件大小,从0x6000->0xC000(加倍)。
然后就是把数据拷贝到新的缓冲区了,前0x6000为原始PE文件的数据,后面0x6000的为shell.dll的数据。
保存文件
保存文件的代码如下,就不细致调试了
BOOL CPACK::SaveFinalFile(LPBYTE pFinalBuf, DWORD pFinalBufSize, CString strFilePath)
{
//修正区段信息中 文件对齐大小(文件对齐大小同内存对齐大小)
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFinalBuf;
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(pFinalBuf + pDosHeader->e_lfanew);
PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pNtHeader);
for (DWORD i = 0; i < pNtHeader->FileHeader.NumberOfSections; i++, pSectionHeader++)
{
pSectionHeader->PointerToRawData = pSectionHeader->VirtualAddress;
}
//清除不需要的目录表信息
//只留输出表,重定位表,资源表
DWORD dwCount = 15;
for (DWORD i = 0; i < dwCount; i++)
{
if (i != IMAGE_DIRECTORY_ENTRY_EXPORT &&
i != IMAGE_DIRECTORY_ENTRY_RESOURCE &&
i != IMAGE_DIRECTORY_ENTRY_BASERELOC )
{
pNtHeader->OptionalHeader.DataDirectory[i].VirtualAddress = 0;
pNtHeader->OptionalHeader.DataDirectory[i].Size = 0;
}
}
//获取保存路径
TCHAR strOutputPath[MAX_PATH] = { 0 };
LPWSTR strSuffix = PathFindExtension(strFilePath);
wcsncpy_s(strOutputPath, MAX_PATH, strFilePath, wcslen(strFilePath));
PathRemoveExtension(strOutputPath);
wcscat_s(strOutputPath, MAX_PATH, L"_cyxvc");
wcscat_s(strOutputPath, MAX_PATH, strSuffix);
//保存文件
HANDLE hNewFile = CreateFile(
strOutputPath,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hNewFile == INVALID_HANDLE_VALUE)
{
MessageBox(NULL, _T("保存文件失败!"), _T("提示"), MB_OK);
return FALSE;
}
DWORD WriteSize = 0;
BOOL bRes = WriteFile(hNewFile, pFinalBuf, pFinalBufSize, &WriteSize, NULL);
if (bRes)
{
CloseHandle(hNewFile);
return TRUE;
}
else
{
CloseHandle(hNewFile);
MessageBox(NULL, _T("保存文件失败!"), _T("提示"), MB_OK);
return FALSE;
}
}
需要注意的是这里是直接把内存映像保存到文件中,并没有压缩。比如看看加壳后节表中各各节的信息。
- text
- rdata
- rsrc
可以看到VirtualAddress
和PointerToRawData
值一样,即磁盘文件偏移和内存映像偏移一样。这里当然可以优化,一般PointerToRawData
都会比VirtualAddress
的值小,以节省磁盘空间。