加壳学习系列(三)-调试程序

本文深入探讨了PE文件加壳技术,详细讲解了加壳前后代码的变化,包括解密函数、壳代码的执行、PE信息的保存和合并过程。通过示例代码展示了如何将壳附加到PE文件,并调整PE头信息以适应新的代码段,最后保存加壳后的文件。此过程涉及PE结构、内存映像和代码加密。
摘要由CSDN通过智能技术生成

之前发了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);
}

先看看pSectionHeaderpLastSection的值
在这里插入图片描述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
    在这里插入图片描述

可以看到VirtualAddressPointerToRawData值一样,即磁盘文件偏移和内存映像偏移一样。这里当然可以优化,一般PointerToRawData都会比VirtualAddress的值小,以节省磁盘空间。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值