标题:[笨笨之菜鸟应该明白之一]IAT表和导入表之间的关联
作者:笨笨学破解
洋名:EasyStudy
日期:NOP掉
工具:什么都有!:)
注意:截图就能省就省了吧!太累人撒!就生硬的文字吧!


一、前言

            大家好!我又来了,一直想写个XXX之二的。但是,现在觉得写不大好!明年吧!呵呵~~
  最近,想发点东西给大家,但是苦于没什么好发的,因为本人菜的可以,所以,发出来又怕怕大家笑话!思量期间还是不发为妙,前几天,我的大大(也就是教我学这行的老大之一)训话了:“做事别招摇!低调!”。不过,从来我觉得他说的到是没什么错的,最近,尤其是前段时间,写了点感慨!被网上的朋友们骂的骂赞的赞。确实,这影响大了到不好,真没想到那么大的反应!前几天研究PE结构也随心发了点心得,给大家分享,真没想到能被精华对待!所以,一直以来都想再发点经典的,支持一下看雪,可是就是找不到蓝本!今天,刚研究到的发出来和大家分享一下!喜欢的收藏,顺便给点意见!不喜欢的就当乐和看看吧!真不知道能不能精华:)

一、导入表:
  哎!别说了,我知道现在很多文章都说过这个玩意了。就花几分钟看看我写的,或许,还真和别人说的不一样了。先不说别的拿工具看一下什么样的是导入表,我们主要用PNHookApi1.00.05新版本的已经支持很多PE类的信息了,没有的,可以去下载区下,或者拿LoadPE就好了,随便拿VC做了个WIN32的程序来研究。如图:
名称:  01.jpg
查看次数: 656
文件大小:  49.5 KB

          我们看到上面是导入模块,下面是导入函数信息,实际猛一看,大家都清楚,可是我想问大家那些东西都是怎么来的吗?没几个人知道了吧!我就来解释一下。我先整体了解导入表,能证明导入表特征的那就算是它的结构了。
//-------------------------------------------------------------------------------------------------
在winnt.h中我找到了它的结构:

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;            // 0 for terminating null import descriptor导入描述符始终为NULL
        DWORD   OriginalFirstThunk;         // RVA to original unbound IAT (PIMAGE_THUNK_DATA)这里后面专门解释
    };
    DWORD   TimeDateStamp;                  // 0 if not bound, 时间日期戳
                                            // -1 if bound, and real date\time stamp
                                            //     in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
                                            // O.W. date/time stamp of DLL bound to (Old BIND)

    DWORD   ForwarderChain;                 // -1 if no forwarders多为0
    DWORD   Name;                           //后面说明
    DWORD   FirstThunk;                     // RVA to IAT (if bound this IAT has actual addresses)后面说明
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

从结构来看我们声明的类型为:
_IMAGE_IMPORT_DESCRIPTOR   
IMAGE_IMPORT_DESCRIPTOR
*PIMAGE_IMPORT_DESCRIPTOR  //这个和前面不一样它表示的是这个结构的指针,前面两个都是结构类型
//------------------------------------------------------------------------------------------------

我们顺着往下看,第一个当然是:OriginalFirstThunk,我特意没把英文翻译过来是希望大家先有个概念!导入表和IAT表是有很大关系的。

     先把前面的内容PUSH保存,等会用着了再POP吧!我先研究一下导入表的16进制
     先最简单的,我们拿WINHEX来打开前面做的程序看吧!我们先可以让LoadPE或PNHookApi查看一下导入表的地址,我这里是0002A000,长度是0000003C,我们看到的这个地址是RVA,需要转换后才能到真正的导入表虚拟地址或物理地址,我们如图(不加图说不清楚了:()
名称:  02.jpg
查看次数: 641
文件大小:  35.7 KB

      图中表示了我们对应的地址为00028000,我们要找的正好是整数,也是区段开头不用换算的,如果不是的话那就可怜点需要换算了。至于换算我以前的贴里已经说了一下,并放出了转换函数大家也不用发愁,拿去用就是了:)。00028000正好就是我们要找的物理偏移地址,拿WINHEX打开看一下,如下:

//--------------------------------------------------------------------------------------------------
Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

00028000   3C A0 02 00 00 00 00 00  00 00 00 00 B4 A3 02 00   <?.........矗..
00028010   F0 A1 02 00 5C A1 02 00  00 00 00 00 00 00 00 00   稹..\?.........
00028020   26 A5 02 00 10 A3 02 00  00 00 00 00 00 00 00 00   &?..?.........
00028030   00 00 00 00 00 00 00 00  00 00 00 00 A4 A3 02 00   ............ぃ..

把我们PUSH进去的知识,POP出来,看看!
我们以DWORD方式整理一下可以看到对应:
3CA00200  //OriginalFirstThunk; 
00000000  //TimeDateStamp
00000000  //ForwarderChain
B4A30200  //Name
F0A10200  //FirstThunk
//--------------------------------------------------------------------------------------------------


      每5个DWORD就代表一个结构,我们先来看第一个地址,0002A03C这个也是个相对虚拟地址,所以嘛!也要转换的,实际一个最简单的办法口算就可以出来了,有朋友就说了,口算……,十六进制呀!方法一学就会!我们不是知道0002A000=00028000吗?我们拿0002A03C-002A000=000003C算出偏移地址,再用00028000+0000003C=0002803C就是物理偏移地址,简单吧!:)是个智商没问题的都会算了吧!我们再看看0002803C是什么样子!

//-----------------------------------------------------------------------------------------------------
Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

00028030   00 00 00 00 00 00 00 00  00 00 00 00 A4 A3 02 00   ............ぃ..
00028040   8A A8 02 00 7A A8 02 00  68 A8 02 00 56 A8 02 00   姩..z?.h?.V?.
00028050   44 A8 02 00 34 A8 02 00  24 A8 02 00 0E A8 02 00   D?.4?.$?..?.
00028060   00 A8 02 00 F0 A7 02 00  E4 A7 02 00 D8 A7 02 00   .?.皈..洄..丕..
00028070   CE A7 02 00 C2 A7 02 00  B2 A7 02 00 A2 A7 02 00   围..搂..钵..ⅶ..

//------------------------------------------------------------------------------------------------------

     可以很清楚的看到0002803C处为0002A3A4,再用上面的方法算出物理偏移地址为000283A4,WINHEX显示如下:

//-----------------------------------------------------------------------------------------------

Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

000283A0   00 00 00 00 C2 01 4C 6F  61 64 4C 69 62 72 61 72   ....?LoadLibrar
000283B0   79 41 00 00 4B 45 52 4E  45 4C 33 32 2E 64 6C 6C   yA..KERNEL32.dll
//------------------------------------------------------------------------------------------------

到这里算是追踪完成了一大半了,既然到了,我必须说明一个格式:

//--------------------------------------------------------------------------
WORD   String 
HINT号 函数名
……
……
模块名
HINT号 函数名
……
……
模块名
……
//---------------------------------------------------------------------------

     也就是说2个字节代表HINT号,后面紧跟的就是函数名,函数名以0x00结尾。在所有函数都罗列完毕后,紧跟的就是这些函数所在的模块名。然后又是函数名,又是模块名……应该够清楚了吧!

二、联系实现

     前面说了怎么获得导入表的方法,但是程序怎么实现了?这个是大家最熟悉的了。:)上面是看了OriginalFirstThunk,我们再来看Name,计算为0002A3B4=000283B4,从上面可以看到它就是函数的模块名KERNEL32.DLL地址。因此我们可以用这样的C语言代码来完成抓导入模块名:

//--------------------------------------------------------------------------------------------
for(;_importDesc->Name!=NULL;i++)
{
  char *szName=(PSTR)ImageRvaToVa(_ntHeader, _dosHeader, (DWORD)_importDesc->Name, 0);
  name[i]=szName;
  _importDesc++;
}

//------------------------------------------------------------------------------------------------

虽然,我们可以看到OriginalFirstThunk和FirstThunk的RVA值是不一样的但是它们指向的内容是一致的。怎么说呢!我们姑且不管转换问题只表关系,类似C的方式如下:
/------------------------------------------
Name->模块名
OriginalFirstThunk->函数名地址->函数名
/-------------------------------------------

    我们可以把OriginalFirstThunk和FirstThunk当作是函数名和信息地址列表这样更准确点,不过,微软给我们一个很好的结构IMAGE_THUNK_DATA来让我们更直观方便的理解上面的内容,他没有直接声明而是见解而来,从中我看到了64位的结构,而这个时候的微软应该早就对64位做了准备了,我想在2000年以前微软就在为64位做准备吗?我是从文件来看的:

//-------------------------------------------------------------------------------------------

winnt.h里同样有:
typedef IMAGE_THUNK_DATA32              IMAGE_THUNK_DATA;  //这里声明了IMAGE_THUNK_DATA=IMAGE_THUNK_DATA32 
typedef PIMAGE_THUNK_DATA32             PIMAGE_THUNK_DATA; //PIMAGE_THUNK_DATA32=PIMAGE_THUNK_DATA

而IMAGE_THUNK_DATA32 被声明为如下格式,
typedef struct _IMAGE_THUNK_DATA32 {
    union {
        PBYTE  ForwarderString;
        PDWORD Function;
        DWORD Ordinal;
        PIMAGE_IMPORT_BY_NAME  AddressOfData;
    } u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;

而上面结构中的PIMAGE_IMPORT_BY_NAME被声明如下:
typedef struct _IMAGE_IMPORT_BY_NAME {
    WORD    Hint;
    BYTE    Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
同过上面的结构我们就可以肯定了我们的分析,函数名的格式都是HINT号+函数名的。

//-----------------------------------------------------------------------------------------------

因此我们最常见的导入表获取方法或代码都类似的处理如下:

//------------------------------------------------------------------------------------------------

  //获取输入表相对虚拟地址:
  _importDesc=(PIMAGE_IMPORT_DESCRIPTOR)_ntHeader->OptionalHeader.DataDirector[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
  if(_importDesc==NULL){return NULL;}  

  //如果是映像处理我们需要转换成虚拟地址,如果是读文件处理我们需要转换成物理偏移地址,这里就用映像法,用的最多的嘛!
  _importDesc=(PIMAGE_IMPORT_DESCRIPTOR)::ImageRvaToVa(_ntHeader,m_pData,(DWORD)_importDesc,NULL);
  //按照分析,我想我们获得地址正好就是00028000的虚拟地址00400000+0002A000=0042A000,(这里假设基地址为00400000)
  //而对模块信息的处理和我们之前分析的方法也是一样的
  //获得相对虚拟地址
  dwThunk=_importDesc->OriginalFirstThunk;
  //转换为虚拟地址  
  _pThunk=(PIMAGE_THUNK_DATA)ImageRvaToVa(_ntHeader,_dosHeader,(DWORD)_importDesc->OriginalFirstThunk,NULL);
  //我想看到这里的朋友都知道了上面结构指针为0002A3A4+00400000=0042A3A4的地方,下面又是按分析的来获取函数名
  char *szFun=(PSTR)ImageRvaToVa(_ntHeader,_dosHeader,(DWORD)_pThunk->u1.AddressOfData->Name, 0);

//-------------------------------------------------------------------------------------------

我想目前看到这里的朋友都知道了些不知道的东西吧!故才有类似这样的C代码来获取函数名信息,很多朋友都知道拿过来用,但是根本不知道为什么要这样做!看完这里就应该知道了,总结关系如下:
00028000:PIMAGE_IMPORT_DESCRIPTOR
0002803C:PIMAGE_THUNK_DATA
000283A4:PIMAGE_IMPORT_BY_NAME

上面三个地址的关系我已经解释了,现在总结了一下,应该对导入表更清楚了点吧!也知道,那些高人们为什么要按上面说的流程来处理导入表吧!

对于HINT的由来我这里就不说明了。

二、IAT表:
  IAT表英文为ImportAddressTableAddress,为什么提英文呢,确实和它还有点关系,顺着翻译为导入地址表的地址,实际确实有点这样的意思哦!
名称:  03.jpg
查看次数: 614
文件大小:  73.8 KB
  和前面分析导入表一样,拿工具获得IAT表地址,为了方便研究我选择了同一文件,从PNHookApi对IAT表的分析来看好象和导入表差不多,实际我想对大家说的是IAT表没那么难,除了一个一唯地址列表,什么也没有,我们找到的地址为:0002A1F0,大小为000001B4,我们从前面那个区段图可以知道,IAT表依然在.idata区段,根据大小来看,我们用00028000还是没问题的:)。有朋友就说了,PNHookApi不是告诉我们它的物理地址是:000281F0吗?就没有人想想我是怎么把它给弄出的呢~
//----------------------------------------------------------------------------------------------

Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

000281F0   A4 A3 02 00 8A A8 02 00  7A A8 02 00 68 A8 02 00   ぃ..姩..z?.h?.
00028200   56 A8 02 00 44 A8 02 00  34 A8 02 00 24 A8 02 00   V?.D?.4?.$?.
00028210   0E A8 02 00 00 A8 02 00  F0 A7 02 00 E4 A7 02 00   .?..?.皈..洄..
00028220   D8 A7 02 00 CE A7 02 00  C2 A7 02 00 B2 A7 02 00   丕..围..搂..钵..
00028230   A2 A7 02 00 92 A7 02 00  7A A7 02 00 6A A7 02 00   ⅶ..挧..z?.j?.

一堆虚拟地址,在winnt.h对IAT表的信息只有:
#define IMAGE_DIRECTORY_ENTRY_IAT            12   // Import Address Table
我想大家都知道了,前面不是说过的吗?那我就不说了,拿WINHEX找到指定的地方:
//-------------------------------------------------------------------------------------------------

实际微软再没做其它了,因为除了一堆相对虚拟地址以外IAT表没别的,真的很难把它看成一个表。这里就在说点大家又不知道的吧!别看一个地址,还真有点学问!


我们可以分三类:
1、API函数结构地址:买个关子后面解释
2、直接是API函数地址
3、HINT号

在说明上面三种区别方法之前我要建议一点汉化问题LoadPE在PE编辑对话框中把SizeOfImage翻译成文件大小是不对的,它的意思应该是镜像大小或者映像大小,就是说在程序运行后占用空间的大小,不是文件的大小。区分方法如下:
//获取映像大小:
DWORD dwsize=GetNTHeaders()->OptionalHeader.SizeOfImage;

if(*pdata!=NULL&&*pdata<=dwsize)
{
  //为函数内部定向地址
}
if(*pdata>dwsize&&*pdata<0x80000000)
{
  //为外调用地址,可能是API函数,或加载的模块函数
}
if(*pdata>=0x80000000)
{
  //为函数的HINT号,也就是0x80000000+HINT号
}


根据上面的区分来划分IAT表中数据的类型。需要说明的是在第一类种我们会发现,它的每个地址都指向了一个PIMAGE_IMPORT_BY_NAME结构,也就是HINT号+函数名的格式,每个地址对应一个这样的结构,准确点讲IAT表的结构可以拿
PIMAGE_IMPORT_BY_NAME代替:)或
PIMAGE_THUNK_DATA代替

三、联系

  我们研究了这么长时间总要有点结论!其它的不说了,我只说关键的,程序运行后IAT表的函数地址是根据导入表中函数名称确定的,而IAT表的函数地址可以直接被设定成某个API函数的地址,也可以按8标识的HINT号,就想MFC类的DLL一样,是按HINT号加载,不是按名称加载的,说到这里大家都明白IAT中的地址就是靠导入表中的函数和标号来确定函数地址的,应该知道ImportRec为什么要获取导入表修复程序了吧!
  还有就是IAT表的地址是导入表中所有导入模块中FirstThunk值最小的值。昨天晚上忙晕了,这里没说明白,实际简单点就是IAT表就是所有导入模块的FirstThunk值的集合。:)



                                                            -EasyStudy For PhantomNet