PE 文件编外篇之导入函数表

    今天把导入函数表弄明白了,在昨天代码的基础上增加列出导入函数部分。

    主程序部分略有改动:现在电脑 CPU 快、内存大,操作内存比硬盘快多了,所以直接把整个文件拷贝到内存再分析!

/* Typedef.h
   数据类型
   四彩
   2015-11-29
*/


#ifndef _TYPEDEF_H
#define _TYPEDEF_H


#ifndef _STDBOOL_H
    typedef char            BOOL;
    #define TRUE            1
    #define FALSE           0
#endif


typedef unsigned char       BYTE;       // 无符号 1 字节
typedef unsigned short int  WORD;       // 无符号 2 字节
typedef unsigned int        DWORD;      // 无符号 4 字节


// 得到一个 DWORD 的高位 WORD 和低位 WORD
#define LWordOfDW(value)  ((WORD)((DWORD)(value) & 0xFFFF))
#define HWordOfDW(value)  ((WORD)((DWORD)(value) >> 16))

// 得到一个 WORD 的高位 BYTE 和低位 BYTE
#define LByteOfW(value)  ((BYTE)((WORD)(value) & 0xFF))
#define HByteOfW(value)  ((BYTE)((WORD)(value) >> 8))

// 把两个 WORD 转化为一个 DWORD
#define Words2DW(HighWord, LowWord) ((((DWORD)(HighWord)) << 16) + (LowWord))

// 把两个 BYTE 转化为一个 WORD
#define Bytes2W(HighByte, LowByte) ((((WORD)(HighByte)) << 8) + (LowByte))


#endif
/*  PE.h
    PE 文件格式
    四彩
    2015-12-02
*/

/*  PE 文件的总体结构:(内存地址有低到高)
        IMAGE_DOS_HEADER(DOS 头)
        DOS stub
        "PE00"(PE 标志)
        IMAGE_FILE_HEADER(文件头)
        IMAGE_OPTIONAL_HEADER(可选头)
        IMAGE_DATA_DIRECTORY(数据块目录)
        IMAGE_SECTION_HEADER(节表)
        .text节区
        .data节区
        其它节区
        不能被映射的其他数据
*/
/*
    虚拟地址:Virtual Address(VA),保护模式下访问内存所使用的逻辑地址。
    装载基址:Image Base,文件装入内存的基址。
              默认情况下,EXE 文件的基址为 0x00400000,DLL 文件的基址为 0x10000000。
    相对虚拟地址:Relative Virtual Address(RVA),在内存中相对于装载基址的偏移量。
    文件偏移地址:File Offset Address(FOA),文件在磁盘上存放时相对于文件开头的偏移量。
                  文件偏移地址从文件的第一个字节开始计数,起始值为 0。
*/
/*
    IMAGE_DATA_DIRECTORY 直接定义在 IMAGE_NT_HEADER 里面,而不在 IMAGE_OPTIONAL_HEADER 里,
    这导致 IMAGE_FILE_HEADER 中的 SizeOfOptionalHeader 字段数值不准。
*/


#ifndef _PE_H
#define _PE_H


#include "Typedef.h"


// =======================================================================================
// DOS 头结构:偏移地址 = 0
//
#define IMAGE_DOS_SIGNATURE 0x5A4D          // e_magic 预定义值,即字符 "MZ"

typedef struct tag_IMAE_DOS_HEADER
{
    //                                         偏移  说明
    WORD    e_magic;                        // 0x00  DOS 可执行文件标识符(= "MZ")
    WORD    e_cblp;                         // 0x02
    WORD    e_cp;                           // 0x04
    WORD    e_crlc;                         // 0x06
    WORD    e_cparhdr;                      // 0x08
    WORD    e_minalloc;                     // 0x0A
    WORD    e_maxalloc;                     // 0x0C
    WORD    e_ss;                           // 0x0E
    WORD    e_sp;                           // 0x10
    WORD    e_csum;                         // 0x12
    WORD    e_ip;                           // 0x14
    WORD    e_cs;                           // 0x16
    WORD    e_lfarlc;                       // 0x18
    WORD    e_ovno;                         // 0x1A
    WORD    e_res[4];                       // 0x1C
    WORD    e_oemid;                        // 0x24
    WORD    e_oeminfo;                      // 0x26
    WORD    e_res2[10];                     // 0x28
    DWORD   e_lfanew;                       // 0x3C  PE 签名的文件偏移地址
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
// ***************************************************************************************


// =======================================================================================
// NT 头结构:偏移地址 = IMAGE_DOS_HEADER.e_lfanew
//
// ---------------------------------------------------------------------------------------
// NT 头结构 —— File 头结构
// Machine 字段预定义值:                    Value      Meaning
#define IMAGE_FILE_MACHINE_UNKNOWN          0
#define IMAGE_FILE_MACHINE_I386             0x014c  // x86
#define IMAGE_FILE_MACHINE_AMD64            0x8664  // x64
#define IMAGE_FILE_MACHINE_IA64             0x0200  // Intel Itanium

/*  Characteristics 是一个标志的集合
    位   预定义值                           含义
    0   IMAGE_FILE_RELOCS_STRIPPED          文件中没有重定向信息。在可执行文件中没有使用。
                                            可执行文件用基址重定向目录表来表示重定向信息。
    1   IMAGE_FILE_EXECUTABLE_IMAGE         可执行文件。
    2   IMAGE_FILE_LINE_NUMS_STRIPPED       没有行数信息。在可执行文件中没有使用。
    3   IMAGE_FILE_LOCAL_SYMS_STRIPPED      没有局部符号信息。在可执行文件中没有使用。
    4   IMAGE_FILE_AGGRESIVE_WS_TRIM        已无效。
    5   IMAGE_FILE_LARGE_ADDRESS_AWARE      应用程序可以处理超过 2 GB 的地址。
    6   未使用
    7   IMAGE_FILE_BYTES_REVERSED_LO        已无效。
    8   IMAGE_FILE_32BIT_MACHINE            希望机器为32位机。这个值永远为 1。
    9   IMAGE_FILE_DEBUG_STRIPPED           没有调试信息。在可执行文件中没有使用。
    10  IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP  该程序不能运行于可移动介质中(如软驱或CD)。
    11  IMAGE_FILE_NET_RUN_FROM_SWAP        程序不能在网上运行。(必须拷贝到内存中执行)
    12  IMAGE_FILE_SYSTEM                   系统文件,如驱动程序。在可执行文件中没有使用。
    13  IMAGE_FILE_DLL                      动态链接库(DLL)。
    14  IMAGE_FILE_UP_SYSTEM_ONLY           不能运行于多处理器系统中。
    15  IMAGE_FILE_BYTES_REVERSED_HI        已无效。
*/
// Characteristics 字段预定义值
#define IMAGE_FILE_RELOCS_STRIPPED          0x0001
#define IMAGE_FILE_EXECUTABLE_IMAGE         0x0002
#define IMAGE_FILE_LINE_NUMS_STRIPPED       0x0004
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED      0x0008
#define IMAGE_FILE_AGGRESIVE_WS_TRIM        0x0010
#define IMAGE_FILE_LARGE_ADDRESS_AWARE      0x0020
#define IMAGE_FILE_BYTES_REVERSED_LO        0x0080
#define IMAGE_FILE_32BIT_MACHINE            0x0100
#define IMAGE_FILE_DEBUG_STRIPPED           0x0200
#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP  0x0400
#define IMAGE_FILE_NET_RUN_FROM_SWAP        0x0800
#define IMAGE_FILE_SYSTEM                   0x1000
#define IMAGE_FILE_DLL                      0x2000
#define IMAGE_FILE_UP_SYSTEM_ONLY           0x4000
#define IMAGE_FILE_BYTES_REVERSED_HI        0x8000

typedef struct tag_IMAGE_FILE_HEADER
{
    WORD    Machine;                        // 0x04  运行所要求的 CPU 类型。
    WORD    NumberOfSections;               // 0x06  节数。
    DWORD   TimeDateStamp;                  // 0x08  文件创建日期和时间
    DWORD   PointerToSymbolTable;           // 0x0C  指向符号表(用于调试)
    DWORD   NumberOfSymbols;                // 0x10  符号表中符号个数(用于调试)
    WORD    SizeOfOptionalHeader;           // 0x14  IMAGE_OPTIONAL_HEADER 结构大小
    WORD    Characteristics;                // 0x16  属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;


// ---------------------------------------------------------------------------------------
// NT 头结构 —— NT 可选头结构
typedef struct tag_IMAGE_OPTIONAL_HEADER
{
    //                                         偏移  说明
    // Standard fields.
    WORD    Magic;                          // 0x18  标志字(Win32 下总是 0x010B)
    BYTE    MajorLinkerVersion;             // 0x1A  链接程序的主版本号
    BYTE    MinorLinkerVersion;             // 0x1B  链接程序的次版本号
    DWORD   SizeOfCode;                     // 0x1C  所有含代码的节的总大小
    DWORD   SizeOfInitializedData;          // 0x20  所有含已初始化数据的节的总大小
    DWORD   SizeOfUninitializedData;        // 0x24  所有含未初始化数据的节的大小
    DWORD   AddressOfEntryPoint;            // 0x28  程序执行入口地址(RVA)
    DWORD   BaseOfCode;                     // 0x2C  代码的区块的起始地址(RVA)
    DWORD   BaseOfData;                     // 0x30  数据的区块的起始地址(RVA)
    // NT additional fields.
    DWORD   ImageBase;                      // 0x34  首选装载基址
    DWORD   SectionAlignment;               // 0x38  内存中节的对齐单位(32 位下为 4K)
    DWORD   FileAlignment;                  // 0x3C  磁盘文件中节的对齐单位(一个扇区大小)
    WORD    MajorOperatingSystemVersion;    // 0x40  要求操作系统最低版本号的主版本号
    WORD    MinorOperatingSystemVersion;    // 0x42  要求操作系统最低版本号的副版本号
    WORD    MajorImageVersion;              // 0x44  可运行于操作系统的主版本号
    WORD    MinorImageVersion;              // 0x46  可运行于操作系统的次版本号
    WORD    MajorSubsystemVersion;          // 0x48  要求最低子系统版本的主版本号
    WORD    MinorSubsystemVersion;          // 0x4A  要求最低子系统版本的次版本号
    DWORD   Win32VersionValue;              // 0x4C  不明。一般为 0 。
    DWORD   SizeOfImage;                    // 0x50  装入内存后的总大小
    DWORD   SizeOfHeaders;                  // 0x54  所有头 + 节表的总大小
    DWORD   CheckSum;                       // 0x58  校检和
    WORD    Subsystem;                      // 0x5C  可执行文件期望的界面子系统
    WORD    DllCharacteristics;             // 0x5E  DllMain() 函数何时被调用,默认为 0
    DWORD   SizeOfStackReserve;             // 0x60  初始化时的栈大小
    DWORD   SizeOfStackCommit;              // 0x64  初始化时实际提交的栈大小
    DWORD   SizeOfHeapReserve;              // 0x68  初始化时保留的堆大小
    DWORD   SizeOfHeapCommit;               // 0x6C  初始化时实际提交的堆大小
    DWORD   LoaderFlags;                    // 0x70  与调试有关,默认为 0
    DWORD   NumberOfRvaAndSizes;            // 0x74  数据目录表的项数
} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;


// ---------------------------------------------------------------------------------------
// NT 头结构 —— 数据目录结构
typedef struct tag_IMAGE_DATA_DIRECTORY
{
    DWORD VirtualAddress;                   // 数据的相对虚拟地址(RVA)
    DWORD Size;                             // 数据的长度
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;


// ---------------------------------------------------------------------------------------
// NT 头结构
#define IMAGE_NT_SIGNATURE 0x00004550           // Signature 字段预定义值,即字符"PE00"

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES    16  // 数据目录表的项数一直是 16
/*  数据目录表各项的含义
  索引号        预定义值                            对应的数据块
    0       IMAGE_DIRECTORY_ENTRY_EXPORT            导出函数表,主要用于 DLL 中的导出函数
    1       IMAGE_DIRECTORY_ENTRY_IMPORT            导入函数表,使用外部函数的数据表
    2       IMAGE_DIRECTORY_ENTRY_RESOURCE          资源数据表
    3       IMAGE_DIRECTORY_ENTRY_EXCEPTION         异常处理表(具体资料不详)
    4       IMAGE_DIRECTORY_ENTRY_SECURITY          安全处理数据表(具体资料不详)
    5       IMAGE_DIRECTORY_ENTRY_BASERELOC         重定位信息表,一般和 DLL 相关
    6       IMAGE_DIRECTORY_ENTRY_DEBUG             调试信息表
    7       IMAGE_DIRECTORY_ENTRY_ARCHITECTURE      版权信息表
    8       IMAGE_DIRECTORY_ENTRY_GLOBALPTR         机器值(MIPS GP)(具体资料不详)
    9       IMAGE_DIRECTORY_ENTRY_TLS               线程信息本地存储表
    10      IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG       装配信息表(具体资料不详)
    11      IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT      输入函数绑定信息表(具体资料不详)
    12      IMAGE_DIRECTORY_ENTRY_IAT               导入函数地址表(与 ImportTable 对应,
                                                            由 Loader 填写的输入函数地址)
    13      IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT      延迟装入的函数信息表(具体资料不详)
    14      IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR    公用组件信息表(具体资料不详)
    15      未使用
*/
#define IMAGE_DIRECTORY_ENTRY_EXPORT            0
#define IMAGE_DIRECTORY_ENTRY_IMPORT            1
#define IMAGE_DIRECTORY_ENTRY_RESOURCE          2
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION         3
#define IMAGE_DIRECTORY_ENTRY_SECURITY          4
#define IMAGE_DIRECTORY_ENTRY_BASERELOC         5
#define IMAGE_DIRECTORY_ENTRY_DEBUG             6
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE      7
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR         8
#define IMAGE_DIRECTORY_ENTRY_TLS               9
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG       10
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT      11
#define IMAGE_DIRECTORY_ENTRY_IAT               12
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT      13
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR    14

typedef struct tag_IMAGE_NT_HEADERS
{
    //                                         偏移  说明
    DWORD                   Signature;      // 0x00  PE 文件签名(= "PE00")
    IMAGE_FILE_HEADER       FileHeader;     // 0x04  文件头结构
    IMAGE_OPTIONAL_HEADER   OptionalHeader; // 0x18  可选 NT 头结构
    IMAGE_DATA_DIRECTORY    DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
                                            // 0x78  数据目录表
} IMAGE_NT_HEADER, *PIMAGE_NT_HEADER;
// ***************************************************************************************


// =======================================================================================
// 节表:偏移地址 = IMAGE_DOS_HEADER.e_lfanew + sizeof(IMAGE_NT_HEADER)
//       项数由 IMAGE_NT_HEADER 中的 FileHeader.NumberOfSections 指定
/*
    一个节中的数据只是属性相同,并不一定是同一种用途的内容,因此仅依靠节表是无法确定和定位
    的,而要由数据目录表来定位。
    节表是节的目录,数据目录表是存储在节里的逻辑元素的目录。

    无论是在内存中还是在磁盘文件中,节都是按基址排列的,而且都要对齐,但对齐值一般不同。
    映射到内存后,所有头和节表的偏移位置与大小均没有变化,而各节基址的偏移位置发生了变化。
    不管节是在文件中还是被加载到内存中,节中数据的位置相对该节的基址是不变的。
        在内存中:数据相对节的起始位置的偏移h = 数据的RVA - 节的RVA
        在文件中:数据相对节的起始位置的偏移h = 数据的FOA – 节的FOA
*/
//
// 节(区块)头结构
//
#define IMAGE_SIZEOF_SHORT_NAME     8       // Name 字段最长 8 字节

// Characteristics 字段预定义值
#define IMAGE_SCN_CNT_CODE                  0x20        // 包含代码
#define IMAGE_SCN_CNT_INITIALIZED_DATA      0x40        // 包含已初始化数据
#define IMAGE_SCN_CNT_UNINITIALIZED_DATA    0x80        // 包含未初始化数据
#define IMAGE_SCN_MEM_DISCARDABLE           0x2000000   // 数据在进程开始后将被丢弃
#define IMAGE_SCN_MEM_NOT_CACHED            0x4000000   // 数据不经过缓存
#define IMAGE_SCN_MEM_NOT_PAGED             0x8000000   // 数据不被交换出内存
#define IMAGE_SCN_MEM_SHARED                0x10000000  // 数据可共享
#define IMAGE_SCN_MEM_EXECUTE               0x20000000  // 可执行
#define IMAGE_SCN_MEM_READ                  0x40000000  // 可读
#define IMAGE_SCN_MEM_WRITE                 0x80000000  // 可写

typedef struct tag_IMAGE_SECTION_HEADER
{
    //                                         偏移  说明
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];  // 0x00  节表名称(仅供编程参考)
    union                                   // 0x08  一般是取后一个,即节的真实长度
    {
        DWORD PhysicalAddress;   // 物理地址(在目标文件中使用,重定位到的地址)
        DWORD VirtualSize;       // 真实长度(在可执行文件中使用,未做对齐处理的实际大小)
    } Misc;
    DWORD   VirtualAddress;                 // 0x0C  装载到内存的基址(内存对齐后的 RVA)
    DWORD   SizeOfRawData;                  // 0x10  在文件中的大小(在磁盘中对齐后的大小)
    DWORD   PointerToRawData;               // 0x14  在文件中的偏移量(从文件头开始算起)
    DWORD   PointerToRelocations;           // 0x18  重定位的偏移(在 OBJ 文件中供调试用)
    DWORD   PointerToLinenumbers;           // 0x1C  行号表的偏移(同上)
    WORD    NumberOfRelocations;            // 0x20  重定位项的数目(同上)
    WORD    NumberOfLinenumbers;            // 0x22  行号表中行号的数目(同上)
    DWORD   Characteristics;                // 0x24  节的属性(如可读、可写、可执行等)
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
// ***************************************************************************************

// =======================================================================================
// ---------------------------------------------------------------------------------------
//  导入函数表:偏移地址 = IMAGE_NT_HEADER.IMAGE_DATA_DIRECTORY[1].VirtualAddress
//              以一个全 0 成员的数组项结尾。
/*
    1、导入函数是被某模块调用,但又不在调用者模块中(实际位于别的 DLL 文件里)的函数。
       调用者模块里只保留一些函数信息,包括函数名及其驻留的 DLL 文件名。
    2、导入函数表就是一个 IMAGE_IMPORT_DESCRIPTOR 数组。
       每个数组项(一个 IMAGE_IMPORT_DESCRIPTOR 结构)对应一个导入的 DLL。
    3、每个 IMAGE_IMPORT_DESCRIPTOR 结构指向两个不同的 IMAGE_THUNK_DATA 结构数组。
       加载前 OriginalFirstThunk 与 FirstThunk 指向的数组内容是相同的,都包含导入信息;
       加载后 FirstThunk 指向的数组指向实际的函数地址。
*/
//
// 导入函数表数组项结构
//
typedef struct tag_IMAGE_IMPORT_BY_NAME
{
    WORD Hint;                      // 本函数在其所驻留 DLL 的导出表中的索引号(非必须)
    char Name[1];                   // 函数名(可变尺寸域)
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

typedef struct tag_IMAGE_THUNK_DATA
{
    union
    {
        DWORD ForwarderString;
        DWORD Function;             // 函数地址
        DWORD Ordinal;              // 函数序号
        DWORD AddressOfData;        // 指向一个 IMAGE_IMPORT_BY_NAME 结构的指针
    } u1;
} IMAGE_THUNK_DATA, *PIMAGE_THUNK_DATA;
/* 通过二进制的最高位判断是否按序号导入:1,按序号导入(Ordinal 的低16位就是导入序号);
                                         0,按名字导入(AddressOfData 指向名字信息)。
*/
#define IMAGE_SNAP(Ordinal)     ((Ordinal) >> 31)       // 判断是否按序号导入
#define IMAGE_ORDINAL(Ordinal)  ((Ordinal) & 0xFFFF)    // 获取导入序号

typedef struct tag_IMAGE_IMPORT_DESCRIPTOR
{
    union                                   // 0x00
    {
        DWORD Characteristics;
        DWORD OriginalFirstThunk;   // 指向一个 IMAGE_THUNK_DATA 数组
    } DUMMYUNIONNAME;
    DWORD TimeDateStamp;
    DWORD ForwarderChain;
    DWORD Name;                             // 0x0C  指向 DLL 文件名的指针
    DWORD FirstThunk;                       // 0x10  指向另一个 IMAGE_THUNK_DATA 数组(RVA)
} IMAGE_IMPORT_DESCRIPTOR, *PIMAGE_IMPORT_DESCRIPTOR;
// ***************************************************************************************


// =======================================================================================
// 输出函数表
// ---------------------------------------------------------------------------------------
/*  函数功能:
        相对虚拟地址转换为文件偏移地址
    参数表:
        pBase = 将整个文件都拷贝到内存的缓冲区基址
        dwRva = 相对虚拟地址
    返回值:
        文件偏移地址
*/
DWORD Rva2Foa(BYTE *pBase, DWORD dwRva);


/*  函数功能:
        文件偏移地址转换为相对虚拟地址
    参数表:
        pBase = 将整个文件都拷贝到内存的缓冲区基址
        dwFoa = 文件偏移地址
    返回值:
        相对虚拟地址
*/
DWORD Foa2Rva(BYTE *pBase, DWORD dwFoa);

// ---------------------------------------------------------------------------------------
/*  函数功能:
        统计 PE 文件全部导入函数的个数
    参数表:
        pBase = 将整个文件都拷贝到内存的缓冲区基址
    返回值:
        全部导入函数的个数
*/
DWORD GetNumOfImpFun(BYTE *pBase);

/*  函数功能:
        取得 PE 文件中指定顺序号的导入函数的函数名或导入序号
    入口参数表:
        pBase = 将整个文件都拷贝到内存的缓冲区基址
        iSN   = 顺序号(从 0 开始)
    出口参数表:
        sDllName = 包含该函数的 dll 模块名
        sFunName = 函数名
        wOrd     = 序号
    返回值:
        函数的导入方式:0 = 函数名导入,1 = 序号导入
        -1 = 顺序号超过导入函数总数
*/
int GetSnapOfImpFun(BYTE *pBase, DWORD iSN, char *sDllName, char *sFunName, WORD *wOrd);
// ***************************************************************************************


#endif
/*  PE.c
    PE 文件格式输出函数
    四彩
    2015-12-01
*/

#include <stdlib.h>
#include <string.h>
#include "PE.h"


// =======================================================================================
// RVA 与 FOA 的互相转换
// ---------------------------------------------------------------------------------------
/*  相对虚拟地址转换为文件偏移地址的转换思路:
    (1)扫描节表,将每个节在内存中的基址加上节的大小得到节的终址,
         根据基址、终址判断 dwRva 是否在该节内范围。
    (2)用 dwRva 减去该节的内存基址,得到 dwRva 相对于该节内存基址的偏移量 RVA'。
         将该节在文件中的基址加上 RVA',得到文件偏移地址。
*/
/*  函数功能:
        相对虚拟地址转换为文件偏移地址
    参数表:
        pBase = 将整个文件都拷贝到内存的缓冲区基址
        dwRva = 相对虚拟地址
    返回值:
        文件偏移地址
*/
DWORD Rva2Foa(BYTE *pBase, DWORD dwRva)
{
    WORD i;
    IMAGE_NT_HEADER         *pNtH;
    IMAGE_SECTION_HEADER    *pSH;

    pNtH = (IMAGE_NT_HEADER *)(pBase + ((IMAGE_DOS_HEADER *)pBase)->e_lfanew);
    if(dwRva < pNtH->OptionalHeader.SizeOfHeaders)          // 在头部
        return dwRva;

    // 遍历各节
    pSH = (IMAGE_SECTION_HEADER *)((BYTE *)pNtH + sizeof(IMAGE_NT_HEADER));
    for(i = 0; i < pNtH->FileHeader.NumberOfSections; i++)
    {
        if(dwRva >= pSH->VirtualAddress && dwRva < pSH->VirtualAddress + pSH->Misc.VirtualSize)
            return pSH->PointerToRawData + dwRva - pSH->VirtualAddress;
        pSH++;
    }

    return 0;
}

// ---------------------------------------------------------------------------------------
/*  文件偏移地址转换为相对虚拟地址的转换思路:
    (1)扫描节表,将每个节在文件中的基址加上节的大小得到节的终址,
         根据基址、终址判断 dwFoa 是否在该节范围内。
    (2)用 dwFoa 减去该节的文件基址,得到 dwFoa 相对于该节文件基址的偏移量 FOA'。
         将该节在内存中的基址加上 FOA',得到相对虚拟地址。
*/
/*  函数功能:
        文件偏移地址转换为相对虚拟地址
    参数表:
        pBase = 将整个文件都拷贝到内存的缓冲区基址
        dwFoa = 文件偏移地址
    返回值:
        相对虚拟地址
*/
DWORD Foa2Rva(BYTE *pBase, DWORD dwFoa)
{
    WORD i;
    IMAGE_NT_HEADER         *pNtH;
    IMAGE_SECTION_HEADER    *pSH;

    pNtH = (IMAGE_NT_HEADER *)(pBase + ((IMAGE_DOS_HEADER *)pBase)->e_lfanew);
    if(dwFoa < pNtH->OptionalHeader.SizeOfHeaders)
        return dwFoa;

    pSH = (IMAGE_SECTION_HEADER *)((BYTE *)pNtH + sizeof(IMAGE_NT_HEADER));
    for(i = 0; i < pNtH->FileHeader.NumberOfSections; i++)
    {
        if(dwFoa >= pSH->PointerToRawData && dwFoa < pSH->PointerToRawData + pSH->Misc.VirtualSize)
            return pSH->VirtualAddress + dwFoa - pSH->PointerToRawData;
        pSH++;
    }
    return 0;
}
// ***************************************************************************************


// =======================================================================================
// 导出函数与导入函数
//
// ---------------------------------------------------------------------------------------
/*  获取所有导入函数的函数名或序号:
    载入 PE 文件到内存,用内存缓冲区基址定位 IMAGE_NT_HEADER 地址。
    用数据目录表中第二项的 VirtualAddress 值,定位第一个 IMAGE_IMPORT_DESCRIPTOR 地址。
    检查 FirstThunk :若不为 0,用 OriginalFirstThunk 定位第一个 IMAGE_THUNK_DATA 数组。
    检查 u1.Ordinal 的二进制最高位:
        若为 1,那么函数是由序号导入的,可以从 Ordinal 的低字提取序号;
        若为 0,那么函数是由函数名导入的,可以由 AddressOfData 指向的 IMAGE_IMPORT_BY_NAME
            取得函数名。
*/
/*  函数功能:
        统计 PE 文件全部导入函数的个数
    参数表:
        pBase = 将整个文件都拷贝到内存的缓冲区基址
    返回值:
        全部导入函数的个数
*/
DWORD GetNumOfImpFun(BYTE *pBase)
{
    DWORD i = 0;
    IMAGE_NT_HEADER         *pNtH;
    IMAGE_IMPORT_DESCRIPTOR *pImpDsc;
    IMAGE_THUNK_DATA        *pThk;

    pNtH = (IMAGE_NT_HEADER *)(pBase + ((IMAGE_DOS_HEADER *)pBase)->e_lfanew);
    if(pNtH->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size == 0)
        return 0;

    pImpDsc = (IMAGE_IMPORT_DESCRIPTOR *)(pBase +
        Rva2Foa(pBase, pNtH->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress));
    while(pImpDsc->Name)
    {
        pThk = (IMAGE_THUNK_DATA *)(pBase + Rva2Foa(pBase, pImpDsc->FirstThunk));
        while(pThk->u1.Ordinal)
        {
            pThk++;
            i++;
        }
        pImpDsc++;
    }

    return i;
}

/*  函数功能:
        取得 PE 文件中指定顺序号的导入函数的函数名或导入序号
    入口参数表:
        pBase = 将整个文件都拷贝到内存的缓冲区基址
        iSN   = 顺序号(从 0 开始)
    出口参数表:
        sDllName = 包含该函数的 dll 模块名
        sFunName = 函数名
        wOrd     = 序号
    返回值:
        函数的导入方式:0 = 函数名导入,1 = 序号导入
        -1 = 顺序号超过导入函数总数
*/
int GetSnapOfImpFun(BYTE *pBase, DWORD iSN, char *sDllName, char *sFunName, WORD *wOrd)
{
    DWORD i = 0;
    IMAGE_NT_HEADER         *pNtH;
    IMAGE_IMPORT_DESCRIPTOR *pImpDsc;
    IMAGE_THUNK_DATA        *pThk;

    pNtH = (IMAGE_NT_HEADER *)(pBase + ((IMAGE_DOS_HEADER *)pBase)->e_lfanew);
    if(pNtH->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size == 0)
        return 0;

    pImpDsc = (IMAGE_IMPORT_DESCRIPTOR *)(pBase +
        Rva2Foa(pBase, pNtH->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress));
    while(pImpDsc->Name)
    {
        pThk = (IMAGE_THUNK_DATA *)(pBase + Rva2Foa(pBase, pImpDsc->FirstThunk));
        while(pThk->u1.Ordinal)
        {
            if(i == iSN)                            // 找到了
            {
                strcpy(sDllName, (char *)(pBase + Rva2Foa(pBase, (pImpDsc->Name))));
                if(IMAGE_SNAP(pThk->u1.Ordinal))    // 序号导入
                {
                    *wOrd = IMAGE_ORDINAL(pThk->u1.Ordinal);
                    return 1;
                }
                else                                // 函数名导入
                {
                    strcpy(sFunName, ((IMAGE_IMPORT_BY_NAME *)(pBase + Rva2Foa(pBase,
                                                        pThk->u1.AddressOfData)))->Name);
                    return 0;
                }
            }

            pThk++;
            i++;
        }
        pImpDsc++;
    }

    return -1;
}
// ***************************************************************************************
/*  PEM.c
    显示 PE 文件格式信息
    四彩
    2015-11-30
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "PE.h"

#define MAX_PATH    260     // 最长字符串长度

void PrintDosHeader(IMAGE_DOS_HEADER *pDosHeader);
void PrintFileHeader(IMAGE_FILE_HEADER FileHeader);
void PrintOptionalHeader(IMAGE_OPTIONAL_HEADER OptHeader);
void PrintDataDircrory(IMAGE_DATA_DIRECTORY DataDirectory);
void PrintSectionHeader(IMAGE_SECTION_HEADER *pSectionHeader);

int main(int argc, char **argv)
{
    FILE    *fp;
    DWORD   i, dwFileLen ;

    BYTE                    *pBase;
    IMAGE_DOS_HEADER        *pDosHeader;
    IMAGE_NT_HEADER         *pNtHeader;
    IMAGE_SECTION_HEADER    *pFstSctHeader, *pSctHeader;

    DWORD   dwNumOfImptFun;
    int     snap;
    char    sDllName[MAX_PATH], sFunName[MAX_PATH];
    WORD    wOrd;

    if(argc != 2)
    {
        printf("Usage : %s exeFileName\n", argv[0]);
        return -1;
    }

    // 将整个文件都拷贝到内存
    fp = fopen(argv[1], "rb");
    if (fp == NULL)
    {
        printf("Error : open file\n");
        return -1;
    }
    fseek(fp, 0, SEEK_END);
    dwFileLen = ftell(fp);
    pBase = (BYTE *)malloc(dwFileLen);
    if(pBase == NULL)
    {
        printf("Error : malloc memory\n");
        return -1;
    }
    fseek(fp, 0, SEEK_SET);
    fread(pBase, dwFileLen, 1, fp);
    fclose(fp);

    // 检查是否为 PE 文件,并给相关结构指针赋值
    pDosHeader = (IMAGE_DOS_HEADER *)pBase;
    if(pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
    {
        printf("Error : NOT DOS file\n");
        free(pBase);
        return -1;
    }
    pNtHeader = (IMAGE_NT_HEADER *)(pBase + ((IMAGE_DOS_HEADER *)pBase)->e_lfanew);
    if(pNtHeader->Signature != IMAGE_NT_SIGNATURE)
    {
        printf("Error : NOT PE file\n");
        free(pBase);
        return -1;
    }
    pFstSctHeader = (IMAGE_SECTION_HEADER *)((BYTE *)pNtHeader + sizeof(IMAGE_NT_HEADER));

    // 显示 IMAGE_DOS_HEADER 信息
    printf("IMAGE_DOS_HEADER :\n");
    PrintDosHeader(pDosHeader);

    // 显示 IMAGE_NT_HEADER 信息
    printf("\nIMAGE_NT_HEADER  :\n");               // Signature
    printf("Signature        : %c%c%d%d\n",         // 低低高高
        LByteOfW(LWordOfDW(pNtHeader->Signature)), HByteOfW(LWordOfDW(pNtHeader->Signature)),
        LByteOfW(HWordOfDW(pNtHeader->Signature)), HByteOfW(HWordOfDW(pNtHeader->Signature)));

    printf("\nIMAGE_FILE_HEADER    :\n");           // IMAGE_FILE_HEADER
    PrintFileHeader(pNtHeader->FileHeader);

    printf("\nIMAGE_OPTIONAL_HEADER       :\n");    // IMAGE_OPTIONAL_HEADER
    PrintOptionalHeader(pNtHeader->OptionalHeader);

    printf("\nIMAGE_DATA_DIRECTORY : ");            // 遍历全部 IMAGE_DATA_DIRECTORY
    printf("\n   NO.  VirtualAddress   Size");
    for(i = 0; i < IMAGE_NUMBEROF_DIRECTORY_ENTRIES; i++)
    {
        printf("\n   %2d", i);
        PrintDataDircrory(pNtHeader->DataDirectory[i]);
    }

    // 遍历显示全部 IMAGE_SECTION_HEADER 信息
    pSctHeader = pFstSctHeader;
    printf("\n\nIMAGE_SECTION_HEADER :");
    for(i = 0; i < pNtHeader->FileHeader.NumberOfSections; i++)
    {
        printf("\nSECTION_HEADER NO    : %02d\nFOA                  : 0x%08X\n", i,
                                    Rva2Foa(pBase, (DWORD)((BYTE *)pSctHeader - pBase)));
        PrintSectionHeader(pSctHeader);
        pSctHeader++;
    }

    // 遍历显示全部导入函数信息
    dwNumOfImptFun = GetNumOfImpFun(pBase);
    printf("\nIMAGE_IMPORT_DESCRIPTOR : %d", dwNumOfImptFun);
    printf("\n SN    DllName      FunName");
    for(i = 0; i < dwNumOfImptFun; i++)
    {
        snap = GetSnapOfImpFun(pBase, i, sDllName, sFunName, &wOrd);
        if(snap == 0)                       // 函数名导入
        {
            printf("\n %02d  % 12s  %s", i, sDllName, sFunName);
        }
        else if(snap == 1)                  // 序号导入
        {
            printf("\n %02d  % 12s  0x%04X", i, sDllName, wOrd);
        }
    }

    free(pBase);
    return 0;
}



// 显示 IMAGE_DOS_HEADER 信息
void PrintDosHeader(IMAGE_DOS_HEADER *pDosHeader)
{
    int i;

    printf("e_magic          : %c%c\n", LByteOfW(pDosHeader->e_magic),
                                                           HByteOfW(pDosHeader->e_magic));
    printf("e_cblp           : 0x%04X\n", pDosHeader->e_cblp);
    printf("e_cp             : 0x%04X\n", pDosHeader->e_cp);
    printf("e_crlc           : 0x%04X\n", pDosHeader->e_crlc);
    printf("e_cparhdr        : 0x%04X\n", pDosHeader->e_cparhdr);
    printf("e_minalloc       : 0x%04X\n", pDosHeader->e_minalloc);
    printf("e_maxalloc       : 0x%04X\n", pDosHeader->e_maxalloc);
    printf("e_ss             : 0x%04X\n", pDosHeader->e_ss);
    printf("e_sp             : 0x%04X\n", pDosHeader->e_sp);
    printf("e_csum           : 0x%04X\n", pDosHeader->e_csum);
    printf("e_ip             : 0x%04X\n", pDosHeader->e_ip);
    printf("e_cs             : 0x%04X\n", pDosHeader->e_cs);
    printf("e_lfarlc         : 0x%04X\n", pDosHeader->e_lfarlc);
    printf("e_ovno           : 0x%04X\n", pDosHeader->e_ovno);
    printf("e_res            : ");
    for(i = 0; i < 4; i++)
        printf("0x%X, ", pDosHeader->e_res[i]);
    printf("\ne_oemid          : 0x%04X\n", pDosHeader->e_oemid);
    printf("e_oeminfo        : 0x%04X\n", pDosHeader->e_oeminfo);
    printf("e_res2           : ");
    for(i = 0; i < 10; i++)
        printf("0x%X, ", pDosHeader->e_res2[i]);
    printf("\ne_lfanew         : 0x%04X\n", pDosHeader->e_lfanew);
}

// 显示 IMAGE_FILE_HEADER 信息
void PrintFileHeader(IMAGE_FILE_HEADER FileHeader)
{
    printf("Machine              : 0x%04X\n", FileHeader.Machine);
    printf("NumberOfSections     : 0x%04X\n", FileHeader.NumberOfSections);
    printf("TimeDateStamp        : 0x%08X\n", FileHeader.TimeDateStamp);
    printf("PointerToSymbolTable : 0x%08X\n", FileHeader.PointerToSymbolTable);
    printf("NumberOfSymbols      : 0x%08X\n", FileHeader.NumberOfSymbols);
    printf("SizeOfOptionalHeader : 0x%04X\n", FileHeader.SizeOfOptionalHeader);
    printf("Characteristics      : 0x%04X\n", FileHeader.Characteristics);
}

// 显示 IMAGE_OPTIONAL_HEADER 信息
void PrintOptionalHeader(IMAGE_OPTIONAL_HEADER OptHeader)
{
    // Standard fields.
    printf("Magic                       : 0x%04X\n", OptHeader.Magic);
    printf("MajorLinkerVersion          : 0x%02X\n", OptHeader.MajorLinkerVersion);
    printf("MinorLinkerVersion          : 0x%02X\n", OptHeader.MinorLinkerVersion);
    printf("SizeOfCode                  : 0x%08X\n", OptHeader.SizeOfCode);
    printf("SizeOfInitializedData       : 0x%08X\n", OptHeader.SizeOfInitializedData);
    printf("SizeOfUninitializedData     : 0x%08X\n", OptHeader.SizeOfUninitializedData);
    printf("AddressOfEntryPoint         : 0x%08X\n", OptHeader.AddressOfEntryPoint);
    printf("BaseOfCode                  : 0x%08X\n", OptHeader.BaseOfCode);
    printf("BaseOfData                  : 0x%08X\n", OptHeader.BaseOfData);

    // NT additional fields.
    printf("ImageBase                   : 0x%08X\n", OptHeader.ImageBase);
    printf("SectionAlignment            : 0x%08X\n", OptHeader.SectionAlignment);
    printf("FileAlignmen                : 0x%08X\n", OptHeader.FileAlignment);
    printf("MajorOperatingSystemVersion : 0x%04X\n", OptHeader.MajorOperatingSystemVersion);
    printf("MinorOperatingSystemVersion : 0x%04X\n", OptHeader.MinorOperatingSystemVersion);
    printf("MajorImageVersion           : 0x%04X\n", OptHeader.MajorImageVersion);
    printf("MinorImageVersion           : 0x%04X\n", OptHeader.MinorImageVersion);
    printf("MajorSubsystemVersion       : 0x%04X\n", OptHeader.MajorSubsystemVersion);
    printf("MinorSubsystemVersion       : 0x%04X\n", OptHeader.MinorSubsystemVersion);
    printf("Win32VersionValue           : 0x%08X\n", OptHeader.Win32VersionValue);
    printf("SizeOfImage                 : 0x%08X\n", OptHeader.SizeOfImage);
    printf("SizeOfHeaders               : 0x%08X\n", OptHeader.SizeOfHeaders);
    printf("CheckSum                    : 0x%08X\n", OptHeader.CheckSum);
    printf("Subsystem                   : 0x%04X\n", OptHeader.Subsystem);
    printf("DllCharacteristics          : 0x%04X\n", OptHeader.DllCharacteristics);
    printf("SizeOfStackReserve          : 0x%08X\n", OptHeader.SizeOfStackReserve);
    printf("SizeOfStackCommit           : 0x%08X\n", OptHeader.SizeOfStackCommit);
    printf("SizeOfHeapReserve           : 0x%08X\n", OptHeader.SizeOfHeapCommit);
    printf("SizeOfHeapCommit            : 0x%08X\n", OptHeader.SizeOfHeapCommit);
    printf("LoaderFlags                 : 0x%08X\n", OptHeader.LoaderFlags);
    printf("NumberOfRvaAndSizes         : 0x%08X\n", OptHeader.NumberOfRvaAndSizes);
}

// 显示 IMAGE_DATA_DIRECTORY 信息
void PrintDataDircrory(IMAGE_DATA_DIRECTORY DataDirectory)
{
    printf("   0x%08X       0x%08X", DataDirectory.VirtualAddress, DataDirectory.Size);
}

// 显示 IMAGE_SECTION_HEADER 信息
void PrintSectionHeader(IMAGE_SECTION_HEADER *pSectionHeader)
{
    if(pSectionHeader->Name)
        printf("Name                 : %s\n", pSectionHeader->Name);
    else
        printf("Name                 : NULL\n");
    printf("Misc.VirtualSize     : 0x%08X\n", pSectionHeader->Misc.VirtualSize);
    printf("VirtualAddress       : 0x%08X\n", pSectionHeader->VirtualAddress);
    printf("SizeOfRawData        : 0x%08X\n", pSectionHeader->SizeOfRawData);
    printf("PointerToRawData     : 0x%08X\n", pSectionHeader->PointerToRawData);
    printf("PointerToRelocations : 0x%08X\n", pSectionHeader->PointerToRelocations);
    printf("PointerToLinenumbers : 0x%08X\n", pSectionHeader->PointerToLinenumbers);
    printf("NumberOfRelocations  : 0x%04X\n", pSectionHeader->NumberOfRelocations);
    printf("NumberOfLinenumbers  : 0x%04X\n", pSectionHeader->NumberOfLinenumbers);
    printf("Characteristics      : 0x%08X\n", pSectionHeader->Characteristics);
}

    照例贴出本程序的分析结果(受字数限制,只贴了今天新增的):

IMAGE_IMPORT_DESCRIPTOR : 59
 SN    DllName      FunName
 00  KERNEL32.dll  DeleteCriticalSection
 01  KERNEL32.dll  EnterCriticalSection
 02  KERNEL32.dll  FreeLibrary
 03  KERNEL32.dll  GetCurrentProcess
 04  KERNEL32.dll  GetCurrentProcessId
 05  KERNEL32.dll  GetCurrentThreadId
 06  KERNEL32.dll  GetLastError
 07  KERNEL32.dll  GetModuleHandleA
 08  KERNEL32.dll  GetProcAddress
 09  KERNEL32.dll  GetStartupInfoA
 10  KERNEL32.dll  GetSystemTimeAsFileTime
 11  KERNEL32.dll  GetTickCount
 12  KERNEL32.dll  InitializeCriticalSection
 13  KERNEL32.dll  LeaveCriticalSection
 14  KERNEL32.dll  LoadLibraryA
 15  KERNEL32.dll  QueryPerformanceCounter
 16  KERNEL32.dll  SetUnhandledExceptionFilter
 17  KERNEL32.dll  Sleep
 18  KERNEL32.dll  TerminateProcess
 19  KERNEL32.dll  TlsGetValue
 20  KERNEL32.dll  UnhandledExceptionFilter
 21  KERNEL32.dll  VirtualProtect
 22  KERNEL32.dll  VirtualQuery
 23    msvcrt.dll  __dllonexit
 24    msvcrt.dll  __getmainargs
 25    msvcrt.dll  __initenv
 26    msvcrt.dll  __lconv_init
 27    msvcrt.dll  __set_app_type
 28    msvcrt.dll  __setusermatherr
 29    msvcrt.dll  _acmdln
 30    msvcrt.dll  _amsg_exit
 31    msvcrt.dll  _cexit
 32    msvcrt.dll  _fmode
 33    msvcrt.dll  _initterm
 34    msvcrt.dll  _iob
 35    msvcrt.dll  _lock
 36    msvcrt.dll  _onexit
 37    msvcrt.dll  _unlock
 38    msvcrt.dll  abort
 39    msvcrt.dll  calloc
 40    msvcrt.dll  exit
 41    msvcrt.dll  fclose
 42    msvcrt.dll  fopen
 43    msvcrt.dll  fprintf
 44    msvcrt.dll  fread
 45    msvcrt.dll  free
 46    msvcrt.dll  fseek
 47    msvcrt.dll  ftell
 48    msvcrt.dll  fwrite
 49    msvcrt.dll  malloc
 50    msvcrt.dll  memset
 51    msvcrt.dll  memcpy
 52    msvcrt.dll  printf
 53    msvcrt.dll  puts
 54    msvcrt.dll  signal
 55    msvcrt.dll  strcpy
 56    msvcrt.dll  strlen
 57    msvcrt.dll  strncmp
 58    msvcrt.dll  vfprint

你可能感兴趣的:(PE 文件编外篇之导入函数表)