当前位置: 主页 > 日志 > 原创程序 >

读取PE导入表,获取程序引用的API名称

最近在黑防上看了一篇文章《简易杀毒软件编写》,讲到了通过扫描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

[日志信息]

该日志于 2009-02-27 02:25 由 redice 发表在 redice's Blog ,你除了可以发表评论外,还可以转载 “读取PE导入表,获取程序引用的API名称” 日志到你的网站或博客,但是请保留源地址及作者信息,谢谢!!    (尊重他人劳动,你我共同努力)
   
验证(必填):   点击我更换验证码

redice's Blog  is powered by DedeCms |  Theme by Monkeii.Lee |  网站地图 |  本服务器由西安鲲之鹏网络信息技术有限公司友情提供

返回顶部