文章标题:使用VC自己动手编写加壳程序(5)——打造自己的壳
主要内容:添加一个新的文件区段,用来存放壳代码,并用8来填充区段。
基本要求:了解VC++6.0基本使用方法;了解PE格式,不熟悉的地方能够通过查阅资料弄懂;
阅读对象:想写壳的新手。高手掠过,本文仅限于写壳入门。
文章类型:原创
文章作者:天漏客 QQ:285252760
完成日期:2009年04月1日
作者主页:www.lilu.name
文章地址:www.lilu.name/Html/heikexuetang/2009-4/3349370008.html
文章说明:允许转载,但最好注明转载出处。
本次内容将添加一个新的区段到原PE文件中,一般加壳程序都会添加一个新的区段用来存放壳代码(这只是一般情况)。然后修改添加区段后的文件的入口点,让程序从添加的壳代码段开始运行。本次内容只添加区段,不修改程序入口点。
添加区段的主要有三个步骤。第一,是修改PE文件头信息。增加一个区段后,要修改NT头中的区段表个数成员,修改可选头的文件镜像大小成员。第二,获取要添加的区段的存放位置和大小,包括文件偏移位置和大小、文件相对虚拟地址和虚拟大小第三,就是将文件写入到文件末尾。
本次源代码:
[点击浏览该文件:PEPacker(5).rar]
添加区段后的区段表对比图如下:

首先添加三个成员函数,如下:
BOOL MakePacking(HANDLE hFile); //生成加壳后的文件
void MakeShell(HANDLE hFile); //生成壳代码段
void EditHeader(); //修改文件头信息
再添加两个成员变量,public型。
LPVOID lpVirtualShell; //壳虚拟内存指针
DWORD SizeOfShell; //壳代码的的大小
DWORD SizeOfImage; //文件镜像大小
MakeShell函数功能是生成壳代码数据,先暂时用0填充该数据段。其功能代码如下:
//生成壳代码段
void CPEPackerDlg::MakeShell(HANDLE hFile)
{
//壳代码段中暂定为用8填充
SizeOfShell=0x100; //大小暂时定为256字节
lpVirtualShell=VirtualAlloc(NULL,SizeOfShell,MEM_RESERVE | MEM_COMMIT,PAGE_READWRITE);
memset(lpVirtualShell,8,SizeOfShell);
}
修改PE头,主要是修改区段表个数和镜像大小。
//修改PE文件头信息,主要是区块个数和镜像大小
void CPEPackerDlg::EditHeader()
{
DWORD dwNumOfSections; //区块个数
DWORD dwFileAlign; //文件粒度(文件对齐大小)
DWORD dwSectionAlign; //块粒度(内存对齐大小)
DWORD dwAlignLastSection; //最后一个区段按内存对齐后的大小
LPVOID lpShellSecTab; //壳区段表指针
IMAGE_SECTION_HEADER SectionHeaderOfShell; //壳代码段的区块表信息
///////以下为修改区段表信息功能,主要是增加壳区段表////////////////////////
//初始化区段表结构
memset(&SectionHeaderOfShell,0,sizeof(SectionHeaderOfShell));
//获取对齐大小数据
dwFileAlign=pOptionalHeader->FileAlignment;
dwSectionAlign=pOptionalHeader->SectionAlignment;
//获取区块表个数
dwNumOfSections=pNtHeader->FileHeader.NumberOfSections;
//设定壳的区块表信息
SectionHeaderOfShell.Misc.PhysicalAddress=SizeOfShell; //原始大小
//在文件中对齐后的大小,除以文件粒度,如果余数为零,就直接使用;否则,就扩充对齐。
SectionHeaderOfShell.SizeOfRawData=(SizeOfShell%dwFileAlign)?(dwFileAlign*(SizeOfShell/dwFileAlign+1)):SizeOfShell;
//区块特征
SectionHeaderOfShell.Characteristics=IMAGE_SCN_MEM_EXECUTE|IMAGE_SCN_MEM_READ|IMAGE_SCN_MEM_WRITE;
memcpy(&SectionHeaderOfShell.Name,".bugsky",7); //区块名称
SectionHeaderOfShell.PointerToRelocations=0; //重定位偏移
SectionHeaderOfShell.NumberOfRelocations=0; //重定位表数目
SectionHeaderOfShell.PointerToLinenumbers=0; //行号表偏移
SectionHeaderOfShell.NumberOfLinenumbers=0; //行号表中行号数目
//对齐最后一个区段后的块大小,来计算壳区段的虚拟地址。
if ((pSectionHeader+dwNumOfSections-1)->SizeOfRawData % dwSectionAlign)
{
dwAlignLastSection=dwSectionAlign*((pSectionHeader+dwNumOfSections-1)->SizeOfRawData / dwSectionAlign+1);
}
else
{
dwAlignLastSection=(pSectionHeader+dwNumOfSections-1)->SizeOfRawData;
}
//获取最后一个区段的相对虚拟地址和虚拟大小
SectionHeaderOfShell.VirtualAddress=(pSectionHeader+dwNumOfSections-1)->VirtualAddress+dwAlignLastSection;
SectionHeaderOfShell.PointerToRawData=(pSectionHeader+dwNumOfSections-1)->PointerToRawData+(pSectionHeader+dwNumOfSections-1)->SizeOfRawData;
//计算壳区段表在PE头中的位置
lpShellSecTab=(LPVOID)((DWORD)pSectionHeader+sizeof(IMAGE_SECTION_HEADER)*dwNumOfSections);
//将壳区段信息拷贝到文件头中
//此方法并不严密,因为没有考虑到PE文件头中是否还有多余的空间,为简化,暂如此操作。
memcpy(lpShellSecTab,&SectionHeaderOfShell,sizeof(SectionHeaderOfShell));
//////////区段表修改完毕,下面修改PE头/////////////////////////////////////////////
//区段表个数加1。
pNtHeader->FileHeader.NumberOfSections++;
//文件镜像增加
pOptionalHeader->SizeOfImage=SizeOfImage+((SizeOfShell % dwSectionAlign)?(dwSectionAlign*(SizeOfShell/dwSectionAlign+1)):SizeOfShell);
}
MakePacking函数功能是将各个内存数据输出,合并为加壳后的文件。其代码如下:
//生成加壳后的文件
BOOL CPEPackerDlg::MakePacking(HANDLE hFile)
{
DWORD dwBufferRead; //实际读取字节大小
DWORD dwNumOfSection; //区段的个数
dwNumOfSection=pNtHeader->FileHeader.NumberOfSections;
//写入原始文件
if (!WriteFile(hFile,lpVirtualtAlloc,SizeOfImage,&dwBufferRead,NULL))
{
return FALSE;
}
//写入壳代码段文件
if (!WriteFile(hFile,lpVirtualShell,(pSectionHeader+dwNumOfSection-1)->SizeOfRawData,&dwBufferRead,NULL))
{
return FALSE;
}
return TRUE;
}
修改OnButtonPacking函数,增加函数调用。OnButtonPacking代码如下:
void CPEPackerDlg::OnButtonPacking()
{
// TODO: Add your control notification handler code here
HANDLE hFile; //文件句柄
//打开文件
hFile=CreateFile(m_FilePathName,GENERIC_READ,FILE_SHARE_READ,NULL,
OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
//如果文件打开失败,就弹出对话框,并返回。
if (hFile==INVALID_HANDLE_VALUE)
{
MessageBox("打开文件失败!","错误提示",MB_OK);
return;
}
/////////////////第三次加的内容/////////////////////////////////////////////
//判断文件格式
if (!IsPE(hFile))
{
m_RichEditProcInfo.ReplaceSel("错误提示:文件不是PE格式!\r\n");
MessageBox("文件不是PE可执行文件","错误提示!",MB_OK);
return;
}
//////////////////////////////////////////////////////////////////////////
//////////第四次内容。分配内存,并载入内存////////////////////////////////////////////
if (!MemAlloc(hFile))
{
m_RichEditProcInfo.ReplaceSel("错误提示:文件加载到内存失败!\r\n");
MessageBox("文件加载到内存失败!","错误提示!",MB_OK);
return;
}
//设定加壳后的文件名,我采取了一个偷懒的简单方法。
//如果要严格做,需要获取文件路径,扩展名等等。
m_FilePathNamePacked=m_FilePathName.Left(m_FilePathName.GetLength()-4)+"_packed.exe";
CloseHandle(hFile);
//创建加壳后的文件句柄
hFile=CreateFile(m_FilePathNamePacked,GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
if (hFile==INVALID_HANDLE_VALUE)
{
MessageBox("创建加壳后文件失败!","错误提示!",MB_OK);
return;
}
////////第五次增加////////////////////////////////////////////////
//生成壳代码段
MakeShell(hFile);
//修改PE文件头
EditHeader();
//输出加壳后的文件
if (!MakePacking(hFile))
{
MessageBox("生成文件时失败!","错误提示!",MB_OK);
return;
}
//关闭文件句柄
CloseHandle(hFile);
//文件写入完毕后,释放内存
MemAllocFree();
//在编辑框中显示信息
m_RichEditProcInfo.ReplaceSel("文件加壳完成!\r\n");
MessageBox("创建文件成功!","成功提示",MB_OK);
}
修改MemAllocFree函数,增加对壳内存释放功能。
void CPEPackerDlg::MemAllocFree()
{
//释放分配的虚拟内存
VirtualFree(lpVirtualtAlloc,0,MEM_DECOMMIT|MEM_RELEASE);
//释放壳代码段的虚拟内存
VirtualFree(lpVirtualShell,0,MEM_DECOMMIT|MEM_RELEASE);
}
至此,整个功能完毕!
使用Windows XP系统自带的记事本程序——notepad.exe进行加壳测试。下图是添加区段后两个记事本程序的PE文件头对比图。

可以看到,区段个数由6个变成了7个。文件镜像大小有0000DC00变成了0000F000。其它的都一样。
以下是用hex_workshop打开的添加区段后的文件,在最后一个区段的位置时的数据图。

从图中可以看出,从DC00开始,数据都是有8组成,说明添加数据成功。
加壳前后的区段表对比图最开始已经放出来了。加壳后的文件多了一个区段“.bugsky”,该区段的偏移和地址都是紧靠文件的最后。
运行加壳后的记事本程序,也成功!