1.数据与指令,以及加载前期相关概念(已完成)
2.pe文件的结构解析(已完成)
3.pe文件load的过程(已完成)
4.壳的处理(已完成)
我们要了解压缩壳处理PE的方式,即是要按照正常的方式载入PE,然后在内存中进行各个操作,完成后写回磁盘中,下图简单的表述了这个过程:
加载待压缩PE文件
壳在模拟系统修改pe文件的时候,有两种方式:
1.直接读取文件,按照各个结构进行拷贝。这里存在一个问题,也是第一篇中介绍过的,磁盘文件的对齐粒度和内存文件对齐粒度不一样。而在PE文件中,各个数据结构依赖于偏移的定位,而相对偏移恰恰是内存中相对基址的偏移,存在一个对齐粒度的转换。第一篇中也提供了文件偏移转内存偏移的函数,在定位各个结构之前,需要调用此函数来转换指定偏移。
2.直接模拟系统loader装载的方法。类似于第三篇中前面部分介绍过系统装载的方法。在一段内存空间内,依此读取pe各个部分的结构,按照各个结构所给定要加载到内存中的偏移,填充的偏移指定的内存处。这样直接的填充方法就避免了文件偏移转换内存偏移,清晰直观。在模拟系统装载的时候,也需要用到一个对其粒度函数的转换。与方式1中的不同,这个转换仅仅是为了填充因为对其粒度与实际大小之间的空隙,满足实际pe装进内存大小的填充。
AlignmentNum(DWORD address, DWORD Alignment)
{
int align = address % Alignment;
return address + Alignment - align;
}
获取目标文件hFile和NT头NtHeader后,要模拟装载PE,首先要知道目标PE头中的下列参数:
DWORD MemAlignment = NtHeader.OptionalHeader.SectionAlignment;//内存对其粒度
DWORD FileAlignment = NtHeader.OptionalHeader.FileAlignment;//磁盘内存对其粒度
DWORD PeSize = AlignmentNum(NtHeader.OptionalHeader.ImageBase, MemAlignment);//载入总大小
根据之前获得经过内存对其的总大小,可以开辟空间
char *pMemPointer = (char *)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, PeSize);
DWORD SecNum = NtHeader.FileHeader.NumberOfSections;//读取节数目
DWORD SizeOfHeader = NtHeader.OptionalHeader.SizeOfHeader;//读取PE头大小
ReadFile(hFile, pMemPointer, SizeOfHeader, NULL, NULL);//从文件读取PE头
获取的了节表头pSectionHeader后,就可以进行装载了。
for (i = 0; i < SecNum; i++)
{
//定位文件第一个节磁盘偏移处
SetFilePointer(hFile, pSectionHeader.PointerToRawData , NULL, FILE_BEGIN);
//读取整个节内存,并转换到内存对其粒度
ReadFile(hFile, (char *)pMemPointer + AlignmentNum(pSectionHeader.VirtualAddress, MemAlignment), pSectionHeader.SizeOfRawData, NULL, NULL);
//读取完之后转到下一个节表
pSectionHeader++;
}
//保存重定位表的原始数据
if(NtHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].Size > 0 )
{
IsRESOURCE == TURE;
DWORD BASERELOC_VA = Headers.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
DWORD BASERELOC_SIZE = Headers.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;
}
//清空重定位表目录
NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress = 0;
NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size = 0;
//保存原始PE资源目录表的信息
if(NtHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].Size > 0 )
{
IsRESOURCE == TURE;
DWORD RESOURCE_VA = NtHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress;
DWORD RESOURCE_SIZE = NtHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].Size;
}
//这里保存要原始资源目录的VA和SIZE,载入内存之后,不进行处理,直接copy到压缩数据节之后。
//pSource压缩源,lInLength数据的大小,lOutLenght判断宏
Compress(PVOID pSource, long lInLength, OUT long &lOutLenght)
{
//packed保存压缩数据的空间,workmem为完成压缩需要使用的空间
BYTE *packed, *workmem;
if ((packed = (BYTE *) malloc(aP_max_packed_size(lInLength))) == NULL ||
(workmem = (BYTE *) malloc(aP_workmem_size(lInLength))) == NULL)
{
return NULL;
}
//调用aP_pack压缩函数
lOutLenght = aP_pack(pSource, packed, lInLength, workmem, NULL, NULL);
if (lOutLenght == APLIB_ERROR)
{
return NULL;
}
if (NULL != workmem)
{
free(workmem);
workmem = NULL;
}
return packed;//返回保存地址
}
[B]区段的处理[/B]
为了保存各个压缩区段的信息,可以定义一个结构体,保存压缩区段信息
typedef struct _CompessSection
{
DWORD VA;
DWORD CompessVA;
DWORD CompessSize;//
LPVOID lpCompessData;//
}CompessSection, *PCompessSection;
首先,要提取出不能压缩的区段,然后根据之前压缩壳的构建,确定要压缩区段写入的地址,应该是紧接在占位区间之后。这里全局变量pMemPointer为之前获得的载入内存基址。
//PeSectionHeader此时指向占位区段最后一个节首地址
DWORD LastSecRva = PeSectionHeader.VirtualAddress;
//最后一个节经过内存对齐后的大小
DWORD LastSecSize = AlignmentNum(pPeSectionHeader[m_iSecNum-1].Misc.VirtualSize,
MemAlignment);
//获得区段压缩后保存的地址
DWORD CompressRva = LastSecRva + LastSecSize;
//获取要压缩区段数
DWORD CompressSecNum = SecNum;
//如果存在资源区段,则不压缩这个区段,被压缩区段数减1
if (IsRESOURCE == TURE)
{
CompressSecNum --;
}
//配置压缩信息
m_pComSec = new CompessSection[CompressSecNum];
int iPos = 0;
int j = 0;
for (unsigned int i = 0; i < SecNum; i++)
{
//跳过资源目录和重定位目录
if (i == 2 )
{
iPos++;
continue;
}
else
{
if (mPeSectionHeader[iPos].SizeOfRawData == 0)
{ //空节不压缩
iPos++;
continue;
}
long lCompressSize = 0;
PVOID pCompressData;
PVOID pInData = (BYTE *)pMemPointer + PeSectionHeader[iPos].VirtualAddress;
//调用Compress函数
pCompressData = Compress(pInData,
PeSectionHeader[iPos].Misc.VirtualSize,
lCompressSize);
//压缩数据指针
m_pComSec[j].lpCompessData = pCompressData;
//解压后内存地址
m_pComSec[j].VA = PeSectionHeader[iPos].VirtualAddress;
//压缩数据加载后的内存地址
m_pComSec[j].CompessVA = CompressRva;
//压缩数据大小
m_pComSec[j].CompessSize = CompressSize;
//下一个节压缩数据加载的地址
iCompressRva += AlignmentNum(CompressSize, MemAlignment);
iPos++;
j++;
}
}
}
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)