先看一下输入表结构:

typedef struct _IMAGE_IMPORT_DESCRIPTOR

{

union

{

 DWORD   Characteristics; //这个union子结构只是给OriginalFirstThunk添了个别名

 DWORDOriginalFirstThunk;

};

DWORD   TimeDateStamp;  

DWORD   ForwarderChain;

DWORD   Name;  //含有指向DLL名字的RVA,即指向DLL名字的指针

DWORD   FirstThunk;  

} IMAGE_IMPORT_DESCRIPTOR;

输入表里边有两个结构很重要已做标记、、

OriginalFirstThunk是一个RVA这个RVA指向一个数组IMAGE_THUNK_DATA

这个数组是这样的一个DWORD类型的集合联合体、、

typedef struct _IMAGE_THUNK_DATA32 {

   union {

       PBYTE  ForwarderString;

       PDWORD Function;

       DWORD Ordinal;

       PIMAGE_IMPORT_BY_NAME  AddressOfData;

   } u1;

} IMAGE_THUNK_DATA32;

一般解释为是一个RVA指向这个结构IMAGE_IMPORT_BY_NAME注意不是数组只是一个结构他真正包含 引入函数的相关信息

typedef struct _IMAGE_IMPORT_BY_NAME {

   WORD    Hint;本函数在其DLL的引出表中的索引号、一些连接器将此值设为0。

   BYTE    Name[1];   有引入函数的函数名

} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

这个数组IMAGE_THUNK_DATA结束的标志就是全0、、

也就是说引入多少函数,就有多少IMAGE_THUNK_DATA结构、、引入多少DLL就有多少引入表结构、、

现在说FirstThunk、、、

FirstThunk与OriginalFirstThunk相似,它也包含指向一个IMAGE_THUNK_DATA结构数组的RVA(当然这是另外一个IMAGE_THUNK_DATA数组)。

为什么会有两个一模一样的数组?是这样的、、

当PE文件被装载到内存时,PE装载器将查找IMAGE_THUNK_DATA

和IMAGE_IMPORT_BY_NAME这些结构数组,以此决定引入函数的地址。然后用引入函数真实地址来替代由FirstThunk指向的IMAGE_THUNK_DATA数组里的元素值。

由OriginalFirstThunk指向的IMAGE_THUNK_DATA数组始终不会改变,所以若还反过头来查找引入函数名,PE装载器还能找到、、

上边说的是如果函数是由函数名引入的情况 、、

有些情况一些函数仅由序数引出,也就是说不能用函数名来调用它们、只能用它们的位置来调用。此时,调用者模块中就不存在该函数的IMAGE_IMPORT_BY_NAME结构。

这种情况下、这么解释分析、、

这种函数的IMAGE_THUNK_DATA值的低位字指示函数序数而最高二进位(MSB)为1。例 如果一个函数只由序数引出且其序数是1234h

那么对应该函数的IMAGE_THUNK_DATA值是80001234h。

Microsoft提供了一个常量来测试dword值的MSB位,就是IMAGE_ORDINAL_FLAG32,其值为80000000h。

也就是说当我们要循环遍历导入函数的时候先判断一下  IMAGE_THUNK_DATA的最高二进制位是否为1  再做定夺、、、

好了基本路线有了部分代码如下:

while (NULL != IMPORT->OriginalFirstThunk)

{   // OriginalFirstThunk为空代表引入表结束一般可以这样作为判断条件

  //有的时候他就是为空但输入表也是有的、、

//若OriginalFirstThunk为0,就改用FirstThunk值不过这样也得不到函数的名字只//是对应的RVA这里也用FisrtThunk去得到引入函数的RVA、、

LPDWORDpOriginalFirstThunk= (LPDWORD)(IMPORT->OriginalFirstThunk - SECRVA +SECVA +pFileAddr);  

//将OriginalFirstThunk代表的RVA  转换为RAW

//此时pOriginalFirstThunk就指向IMAGE_THUNK_DATA结构它加加就是

//IMAGE_THUNK_DATA数组的下一个元素   四个字节嘛、、记住这里的用法、、

LPDWORD FirstThunk =(LPDWORD)(IMPORT->FirstThunk )

//这是每个存放API的RVA下边则是存放API的  RAW

//LPDWORD RAW=(LPDWORD)(IMPORT->FirstThunk- SECRVA +SECVA +pFileAddr )

    while(*pOriginalFirstThunk)

{

    if (*pOriginalFirstThunk & IMAGE_ORDINAL_FLAG32)

{

    DWORD Hint = (*pOriginalFirstThunk)&0x7fffffff;  //取低位、、

    printf("序号引入= = = %x\n",Hint);

}

else

{

    PIMAGE_IMPORT_BY_NAME NAME =

                         PIMAGE_IMPORT_BY_NAME(*pOriginalFirstThunk - SECRVA +SECVA +(DWORD)pFileAddr);

    printf("HINT = %x\n",NAME->Hint);

    printf("NAME = %s\n",NAME->Name);

}

pOriginalFirstThunk++;

FirstThunk++;

}

IMPORT++; }

}

当PE文件加载到内存时FirstThunk的内容就变为真正的API的地址了、

再看导出表结构、、

typedef struct _IMAGE_EXPORT_DIRECTORY {

   DWORD   Characteristics;

   DWORD   TimeDateStamp;

   WORD    MajorVersion;

   WORD    MinorVersion;

   DWORD   Name;

   DWORD   Base;

   DWORD   NumberOfFunctions;

   DWORD   NumberOfNames;

   DWORD   AddressOfFunctions;     // RVA from base of p_w_picpath

   DWORD   AddressOfNames;        // RVA from base of p_w_picpath

   DWORD   AddressOfNameOrdinals;  // RVA from base of p_w_picpath

} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;


导出表的概念:

输出函数是用来给其他程序使用的。其他程序如果知道了某个输出函数的入口地址(就是实现这个函数功能的代码开始的地方),就可以转到那里去执行。一个PE文件中,如果有有输出函数,一般都不是一个。所以有一个数组来保存每个输出函数的入口地址。

在PE文件中,提供两种方法,来找到某个输出函数的入口地址。


第一种方法是通过入口地址数组序号,就是说知道是入口地址数组中的第几个元素,这样就可以得到里面的入口地址。

第二种方法是通过函数名,通过比较函数名,然后得到对应该函数名的入口地址数组的序号,从而得到该函数名的对应函数的入口地址。


导出表的遍历比较简单、、

因为导出函数的地址的RVA有 导出函数的名字的RVA有导出函数的序号的RVA有、、

直接遍历即可、、

LPDWORD DLLNAME = (LPDWORD)(EXPORT->Name- SECRVA + SECVA +pFileAddr);

printf("EXPORT  DLLNAME =======  %s\n\n\n",DLLNAME);//dll名字的输出

LPDWORD FUNCADDR = (LPDWORD)(EXPORT->AddressOfFunctions- SECRVA + SECVA +pFileAddr);//函数RVA的RAW

LPWORD ORDADDR = (LPWORD)(EXPORT->AddressOfNameOrdinals- SECRVA + SECVA +pFileAddr);//函数序号索引的RAW

LPDWORD FUNCNAME = (LPDWORD)(EXPORT->AddressOfNames- SECRVA + SECVA +pFileAddr);//函数名字的RAW

for(DWORD i=0; i<EXPORT->NumberOfFunctions; i++)

{

LPDWORD REALFUNCNAME=(LPDWORD)(*FUNCNAME-SECRVA + SECVA +pFileAddr);//函数名字的输出 再转换一次才是名字的RAW

、、

、、

、、 输出即可、、

FUNCNAME++;  //数组加加、、

ORDADDR++;

FUNCADDR++;

}

}

完、