最近在黑防上看了一篇文章《简易杀毒软件编写》,讲到了通过扫描PE文件中危险API的方法判断PE文件是否为病毒的思路,很不错。但是在关键的地方,作者却用了一句“黑防以前的文章有关于查看PE导入表的,在这里不aoshu”的话,将如何扫描PE导入表搪塞过去了。狂郁闷呀。“以前的黑防”到底是哪一期呀??最后到黑防网站上下到了这篇文章的代码,代码中只有关于如何查找文件,如何获取进程列表以及如何判断一个文件是pe文件三个函数,根本没有关于扫描PE导入表的相关方法。
看来要靠自己动手了。以前就像好好学习一下PE结构,但是后几次都知难而退了。这次一定要拿下。
到"看雪"找来了关于PE结构的资料,讲得很详细,也很好。唯一不满的地方是那些例子程序全是用Win32汇编写的,呵呵,勉强能认懂...花了整整一个下午总算从"dos_header"看到了"import table(导入表)"。没看一部分我都用VC++试验一下。前面都还顺利,到了导入表就挂了。这个结构太复杂了,加上rva和offset的装换,头越来越大...
第二天又花了一个上午,硬着头皮看那些资料上的Win32汇编,功夫不负有心人。在我尝试N次后,终于在14:48分搞定了。
啥都先别说,先发个截图,自我安慰一下 呵呵
简单总结一下吧:
读取PE文件的信息,并不一定要先将PE文件映射到内存中。通过移动文件读写指针同样可以达到PE文件任何信息(当然包括导入表)的目的。其实将文件映射到内存中,文件的数据并没有发生改变(PE文件被装载器装在到内存中时才会发生变化)。因此读内存中的文件映射和读硬盘上的文件数据是一模一样的。不过我还是建议先将文件映射到内存中,因为频繁的移动文件读写指针,读取数据真的很讨厌。在内存中就方便多了,指针做个运算就完成了。
rva转offset真的很麻烦,不过知道原理后,还是挺简单的。我说一下思路:
1 通过dos_header获取nt_header的offset;
2 通过nt_header.FileHeader.NumberOfSections获取PE中节的数目;
3 PE节表就紧跟在nt_header后面,遍历PE节表,如果rva>=section.VirtualAddress且
rva<section.VirtualAddress+section.SizeOfRawData那么就可以确定该rva是落在该节中了
4 section是上面所确定的节,rva-section.VirtualAddress+section.PointerToRawData就是rva对应的offset值
写个函数实现该功能:
//根据相对虚拟(rva)地址计算偏移地址(offset)
UINT rva2offset(IMAGE_NT_HEADERS * pimage_nt_header,UINT rva)
{
IMAGE_SECTION_HEADER *pimage_section_header;
UINT sectionnum,i;
//取得节表项数目
sectionnum=pimage_nt_header->FileHeader.NumberOfSections;
//取得第一个节表项
pimage_section_header=(IMAGE_SECTION_HEADER *) ((BYTE *)pimage_nt_header+sizeof(IMAGE_NT_HEADERS));
for(i=0;i<sectionnum;i++)
{
if((pimage_section_header->VirtualAddress<=rva)
&&rva<(pimage_section_header->VirtualAddress+pimage_section_header->SizeOfRawData))
return rva-pimage_section_header->VirtualAddress+pimage_section_header->PointerToRawData;
pimage_section_header++;
}
return 0;
}
程序的完整代码如下:
// ImportTable
#include <windows.h>
#include <stdio.h>
//判断指定缓冲区是否为全0
//全零:TRUE 非全零:FALSE
BOOL allzero(BYTE data[],int datasize)
{
int i=0;
for(i=0;i<datasize;i++)
{
if(data[i]) return FALSE;
}
return TRUE;
}
//根据相对虚拟(rva)地址计算偏移地址(offset)
UINT rva2offset(IMAGE_NT_HEADERS * pimage_nt_header,UINT rva)
{
IMAGE_SECTION_HEADER *pimage_section_header;
UINT sectionnum,i;
//取得节表项数目
sectionnum=pimage_nt_header->FileHeader.NumberOfSections;
//取得第一个节表项
pimage_section_header=(IMAGE_SECTION_HEADER *)
((BYTE *)pimage_nt_header+sizeof(IMAGE_NT_HEADERS));
for(i=0;i<sectionnum;i++)
{
if((pimage_section_header->VirtualAddress<=rva)
&&rva<(pimage_section_header->VirtualAddress+pimage_section_header->SizeOfRawData))
return rva-pimage_section_header->VirtualAddress+pimage_section_header->PointerToRawData;
pimage_section_header++;
}
return 0;
}
BOOL ReadImportTable(char FileName[MAX_PATH])
{
IMAGE_DOS_HEADER *pimage_dos_header;
IMAGE_NT_HEADERS *pimage_nt_header;
IMAGE_IMPORT_DESCRIPTOR *pimage_import_descriptor;
IMAGE_THUNK_DATA *pimage_thunk_data;
IMAGE_IMPORT_BY_NAME *pimage_import_by_name;
HANDLE hFile;
HANDLE hMapping;
LPVOID pMapping;
ULONG rva_ofimporttable,offset_importtable,rva_name,offset_name;
ULONG rva_image_thunk_data,offset_image_thunk_data;
ULONG rva_image_import_by_name,offset_image_import_by_name;
char *pname;
hFile=CreateFile(FileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if(INVALID_HANDLE_VALUE==hFile) return FALSE;
//将PE文件映射到内存
hMapping=CreateFileMapping(hFile,NULL,PAGE_READONLY,0,0,0);
if(!hMapping) return FALSE;
pMapping=MapViewOfFile(hMapping,FILE_MAP_READ,0,0,0);
if(!pMapping) return FALSE;
//取得dos_header的地址
pimage_dos_header=(IMAGE_DOS_HEADER *)pMapping;
if(pimage_dos_header->e_magic!=IMAGE_DOS_SIGNATURE)
{
printf("无效的PE文件!
");
return FALSE;
}
//计算nt_header的地址
pimage_nt_header=(IMAGE_NT_HEADERS *)((BYTE *)pMapping+pimage_dos_header->e_lfanew);
if(pimage_nt_header->Signature!=IMAGE_NT_SIGNATURE)
{
printf("无效的PE文件!
");
return FALSE;
}
//导入表的相对虚拟地址(RVA)
rva_ofimporttable=pimage_nt_header->OptionalHeader.DataDirectory[1].VirtualAddress;
//根据相对虚拟(rva)地址计算偏移地址(offset)
offset_importtable=rva2offset(pimage_nt_header,rva_ofimporttable);
if(!offset_importtable) return FALSE;
//取得导入表的地址
pimage_import_descriptor=(IMAGE_IMPORT_DESCRIPTOR *)((BYTE *)pMapping+offset_importtable);
while(!allzero((BYTE *)pimage_import_descriptor,sizeof(IMAGE_IMPORT_DESCRIPTOR)))
{
//注意:这里pimage_import_descriptor->Name也是一个rva值,不是一个字符串
rva_name=pimage_import_descriptor->Name;
offset_name=rva2offset(pimage_nt_header,rva_name);
pname=(BYTE *)pMapping+offset_name;
printf("
Dll FileName: %s
",pname);
//指向一个 IMAGE_THUNK_DATA 结构数组的RVA
rva_image_thunk_data=pimage_import_descriptor->OriginalFirstThunk;
if(!rva_image_thunk_data)
rva_image_thunk_data=pimage_import_descriptor->FirstThunk;
//指向一个 IMAGE_THUNK_DATA 结构数组的offset
offset_image_thunk_data=rva2offset(pimage_nt_header,rva_image_thunk_data);
//指向一个 IMAGE_THUNK_DATA 结构数组的指针
pimage_thunk_data=(IMAGE_THUNK_DATA *)((BYTE *)pMapping+offset_image_thunk_data);
while(pimage_thunk_data->u1.Ordinal)
{
/*如果pimage_thunk_data->u1.Ordinal的最高二进位为1,
那么函数是由序数引入的,可以从该值的低字节提取序数。*/
if((pimage_thunk_data->u1.Ordinal)&IMAGE_ORDINAL_FLAG32)
printf(" %x (ord.)
",(pimage_thunk_data->u1.Ordinal)& 0x0FFFF);
else/*如果元素值的最高二进位为0,就可将该值作为RVA转入IMAGE_IMPORT_BY_NAME结构,
跳过 Hint 就是函数名字了。*/
{
//取得IMAGE_IMPORT_BY_NAME结构的rva
rva_image_import_by_name=pimage_thunk_data->u1.Ordinal;
//取得IMAGE_IMPORT_BY_NAME结构的offset
offset_image_import_by_name=rva2offset(pimage_nt_header,rva_image_import_by_name);
//取得IMAGE_IMPORT_BY_NAME结构的指针
pimage_import_by_name=(IMAGE_IMPORT_BY_NAME *)((BYTE *)pMapping+offset_image_import_by_name);
printf(" %s
",&pimage_import_by_name->Name);
}
pimage_thunk_data++;
}
pimage_import_descriptor++;
}
return TRUE;
}
int main()
{
ReadImportTable("c:\windows\system32\cmd.exe");
return 0;
}
附源程序:
File: Click to Download
读取PE导入表,获取程序引用的API名称
[日志分享]
[日志信息]
该日志于 2009-02-27 02:25 由 redice 发表在 redice's Blog ,你除了可以发表评论外,还可以转载 “读取PE导入表,获取程序引用的API名称” 日志到你的网站或博客,但是请保留源地址及作者信息,谢谢!! (尊重他人劳动,你我共同努力)
呵呵,谢谢
VaTG790i.最好的<a href=http://www.kyfei.com>网站推广软件</a>,
非常好
....................
;ui;普i;uighur;ui;ui;个
在unix网络编程中看到了关于TCP/IP的一些内容,我感觉还是写的不够。正在下载中,一定
下载地址呢