PE中的import table/IAT 分析

PE中的import table/IAT 分析

本文将通过一个实例说明PE结构中的import table及import address table(IAT).

 

在data directory中有两项:IMAGE_DIRECTORY_ENTRY_IMPORT(1)和IMAGE_DIRECTORY_ENTRY_IAT(12),IMAGE_DIRECTORY_ENTRY_IMPORT指向了该PE文件中所有的输入信息,而IMAGE_DIRECTORY_ENTRY_IAT指向了该PE文件中的导入地址表。现在不必关心这两个表的关系,在之后的分析中将渐渐明朗。

 

如果用C/C++程序来分析import table或IAT的结构不是很合适,因为CRT会插入太多的代码到最终的PE文件中,所以我们选择用汇编代码作为例子,好处就是所有的导入函数都是自己的代码中确确实实使用到的,一目了然。

 

我们使用现成的一个例子的分析:(摘自Win32ASM Programming 3rd Edition)

  1. ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>  
  2. ; Sample code for < Win32ASM Programming 3rd Edition>  
  3. ; by 罗云彬, http://www.win32asm.com.cn  
  4. ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>  
  5. ; FirstWindow.asm  
  6. ; 窗口程序的模板代码  
  7. ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>  
  8. ; 使用 nmake 或下列命令进行编译和链接:  
  9. ; ml /c /coff FirstWindow.asm  
  10. ; Link /subsystem:windows FirstWindow.obj  
  11. ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>  
  12.         .386  
  13.         .model flat,stdcall  
  14.         option casemap:none  
  15. ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>  
  16. ; Include 文件定义  
  17. ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>  
  18. include     windows.inc  
  19. include     gdi32.inc  
  20. includelib  gdi32.lib  
  21. include     user32.inc  
  22. includelib  user32.lib  
  23. include     kernel32.inc  
  24. includelib  kernel32.lib  
  25. ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>  
  26. ; 数据段  
  27. ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>  
  28.         .data?  
  29. hInstance   dd      ?  
  30. hWinMain    dd      ?  
  31.   
  32.         .const  
  33. szClassName db  'MyClass',0  
  34. szCaptionMain   db  'My first Window !',0  
  35. szText      db  'Win32 Assembly, Simple and powerful !',0  
  36. ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>  
  37. ; 代码段  
  38. ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>  
  39.         .code  
  40. ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>  
  41. ; 窗口过程  
  42. ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>  
  43. _ProcWinMain    proc    uses ebx edi esi hWnd,uMsg,wParam,lParam  
  44.         local   @stPs:PAINTSTRUCT  
  45.         local   @stRect:RECT  
  46.         local   @hDc  
  47.   
  48.         mov eax,uMsg  
  49. ;********************************************************************  
  50.         .if eax ==  WM_PAINT  
  51.             invoke  BeginPaint,hWnd,addr @stPs  
  52.             mov @hDc,eax  
  53.   
  54.             invoke  GetClientRect,hWnd,addr @stRect  
  55.             invoke  DrawText,@hDc,addr szText,-1,/  
  56.                 addr @stRect,/  
  57.                 DT_SINGLELINE or DT_CENTER or DT_VCENTER  
  58.   
  59.             invoke  EndPaint,hWnd,addr @stPs  
  60. ;********************************************************************  
  61.         .elseif eax ==  WM_CLOSE  
  62.             invoke  DestroyWindow,hWinMain  
  63.             invoke  PostQuitMessage,NULL  
  64. ;********************************************************************  
  65.         .else  
  66.             invoke  DefWindowProc,hWnd,uMsg,wParam,lParam  
  67.             ret  
  68.         .endif  
  69. ;********************************************************************  
  70.         xor eax,eax  
  71.         ret  
  72.   
  73. _ProcWinMain    endp  
  74. ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>  
  75. _WinMain    proc  
  76.         local   @stWndClass:WNDCLASSEX  
  77.         local   @stMsg:MSG  
  78.   
  79.         invoke  GetModuleHandle,NULL  
  80.         mov hInstance,eax  
  81.         invoke  RtlZeroMemory,addr @stWndClass,sizeof @stWndClass  
  82. ;********************************************************************  
  83. ; 注册窗口类  
  84. ;********************************************************************  
  85.         invoke  LoadCursor,0,IDC_ARROW  
  86.         mov @stWndClass.hCursor,eax  
  87.         push    hInstance  
  88.         pop @stWndClass.hInstance  
  89.         mov @stWndClass.cbSize,sizeof WNDCLASSEX  
  90.         mov @stWndClass.style,CS_HREDRAW or CS_VREDRAW  
  91.         mov @stWndClass.lpfnWndProc,offset _ProcWinMain  
  92.         mov @stWndClass.hbrBackground,COLOR_WINDOW + 1  
  93.         mov @stWndClass.lpszClassName,offset szClassName  
  94.         invoke  RegisterClassEx,addr @stWndClass  
  95. ;********************************************************************  
  96. ; 建立并显示窗口  
  97. ;********************************************************************  
  98.         invoke  CreateWindowEx,WS_EX_CLIENTEDGE,offset szClassName,offset szCaptionMain,/  
  99.             WS_OVERLAPPEDWINDOW,/  
  100.             100,100,600,400,/  
  101.             NULL,NULL,hInstance,NULL  
  102.         mov hWinMain,eax  
  103.         invoke  ShowWindow,hWinMain,SW_SHOWNORMAL  
  104.         invoke  UpdateWindow,hWinMain  
  105. ;********************************************************************  
  106. ; 消息循环  
  107. ;********************************************************************  
  108.         .while  TRUE  
  109.             invoke  GetMessage,addr @stMsg,NULL,0,0  
  110.             .break  .if eax == 0  
  111.             invoke  TranslateMessage,addr @stMsg  
  112.             invoke  DispatchMessage,addr @stMsg  
  113.         .endw  
  114.         ret  
  115.   
  116. _WinMain    endp  
  117. ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>  
  118. start:  
  119.         call    _WinMain  
  120.         invoke  ExitProcess,NULL  
  121. ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>  
  122.         end start  
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Sample code for < Win32ASM Programming 3rd Edition> ; by 罗云彬, http://www.win32asm.com.cn ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; FirstWindow.asm ; 窗口程序的模板代码 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 使用 nmake 或下列命令进行编译和链接: ; ml /c /coff FirstWindow.asm ; Link /subsystem:windows FirstWindow.obj ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .386 .model flat,stdcall option casemap:none ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Include 文件定义 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> include windows.inc include gdi32.inc includelib gdi32.lib include user32.inc includelib user32.lib include kernel32.inc includelib kernel32.lib ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 数据段 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .data? hInstance dd ? hWinMain dd ? .const szClassName db 'MyClass',0 szCaptionMain db 'My first Window !',0 szText db 'Win32 Assembly, Simple and powerful !',0 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 代码段 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .code ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 窗口过程 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> _ProcWinMain proc uses ebx edi esi hWnd,uMsg,wParam,lParam local @stPs:PAINTSTRUCT local @stRect:RECT local @hDc mov eax,uMsg ;******************************************************************** .if eax == WM_PAINT invoke BeginPaint,hWnd,addr @stPs mov @hDc,eax invoke GetClientRect,hWnd,addr @stRect invoke DrawText,@hDc,addr szText,-1,/ addr @stRect,/ DT_SINGLELINE or DT_CENTER or DT_VCENTER invoke EndPaint,hWnd,addr @stPs ;******************************************************************** .elseif eax == WM_CLOSE invoke DestroyWindow,hWinMain invoke PostQuitMessage,NULL ;******************************************************************** .else invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .endif ;******************************************************************** xor eax,eax ret _ProcWinMain endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> _WinMain proc local @stWndClass:WNDCLASSEX local @stMsg:MSG invoke GetModuleHandle,NULL mov hInstance,eax invoke RtlZeroMemory,addr @stWndClass,sizeof @stWndClass ;******************************************************************** ; 注册窗口类 ;******************************************************************** invoke LoadCursor,0,IDC_ARROW mov @stWndClass.hCursor,eax push hInstance pop @stWndClass.hInstance mov @stWndClass.cbSize,sizeof WNDCLASSEX mov @stWndClass.style,CS_HREDRAW or CS_VREDRAW mov @stWndClass.lpfnWndProc,offset _ProcWinMain mov @stWndClass.hbrBackground,COLOR_WINDOW + 1 mov @stWndClass.lpszClassName,offset szClassName invoke RegisterClassEx,addr @stWndClass ;******************************************************************** ; 建立并显示窗口 ;******************************************************************** invoke CreateWindowEx,WS_EX_CLIENTEDGE,offset szClassName,offset szCaptionMain,/ WS_OVERLAPPEDWINDOW,/ 100,100,600,400,/ NULL,NULL,hInstance,NULL mov hWinMain,eax invoke ShowWindow,hWinMain,SW_SHOWNORMAL invoke UpdateWindow,hWinMain ;******************************************************************** ; 消息循环 ;******************************************************************** .while TRUE invoke GetMessage,addr @stMsg,NULL,0,0 .break .if eax == 0 invoke TranslateMessage,addr @stMsg invoke DispatchMessage,addr @stMsg .endw ret _WinMain endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> start: call _WinMain invoke ExitProcess,NULL ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> end start

 

通过stud_pe(一个很好的学习PE的工具)我们清楚的看到在这个例子中我们使用了如下导入函数:

user32.dll:

DispatchMessageA

DrawTextA

EndPaint

GetClientRect

GetMessageA

DestroyWindow

PostQuitMessage

RegisterClassExA

ShowWindow

TranslateMessage

UpdateWindow

DefWindowProcA

CreateWindowExA

LoadCursorA

BeginPaint

 

kernel32.dll:

GetModuleHandleA

ExitProcess

RtlZeroMemory

 

现在我们从Data Directory开始分析。(以下截图均来自Stud_PE)

 

IMAGE_DIRECTORY_ENTRY_IMPORT:

 

IMAGE_DIRECTORY_ENTRY_IAT:

 

在继续之前我们需要了解两个东西:

1. Data Directory的每一项前四字节表示虚拟地址,后四字节表示大小,本文中不关心。不熟悉的读书可以查阅PE结构获取更多信息。

2. 这里的地址是虚拟地址,也就是载入内存以后的地址。我们如何在文件中也就是物理硬盘上找到对应的内容呢?其实通过Optional Header中的FileAlignment和SectionAlignment可以实现虚拟地址和物理地址的转化。有兴趣的读书可以查阅PE结构获取更多信息,本文将使用stud_pe提供的转化工具完成相应工作。

 

接下来开始我们逐步分析:

Import table的地址:00002090        通过stud_pe找到物理地址:00000690

IAT的地址:00002000                     通过stud_pe找到物理地址:00000600

 

那么00000690和00000600分别在文件的哪个section呢?通过查看该PE的section table我们发现所有的相关信息都存放在.rdata段了。说到这里简单提一下,data directory中指向的内容都是存在各个section中的,至于存在哪个section主要看section的读写属性,当然也可以另起一个section单独存放某个data entry的内容,比如.reloc就作为一个单独的段存放了所有的重定位信息。

 

为了方便,我们把整个.rdata段拿出来分析:

 

 

在继续之前,我们先大概了解一下import table的知识:

这个图再清晰不过了,不过要彻底搞明白还是要结合例子分析:

输入表是一个 IMAGE_IMPORT_DESCRIPTOR数据结构的数组,这个结构定义如下:

  1. typedef struct _IMAGE_IMPORT_DESCRIPTOR {  
  2.     union {  
  3.         DWORD   Characteristics;  
  4.         DWORD   OriginalFirstThunk;  
  5.     } DUMMYUNIONNAME;  
  6.     DWORD   TimeDateStamp;  
  7.     DWORD   ForwarderChain;  
  8.     DWORD   Name;  
  9.     DWORD   FirstThunk;  
  10. } IMAGE_IMPORT_DESCRIPTOR;  
typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; DWORD OriginalFirstThunk; } DUMMYUNIONNAME; DWORD TimeDateStamp; DWORD ForwarderChain; DWORD Name; DWORD FirstThunk; } IMAGE_IMPORT_DESCRIPTOR;

这个结构总共20个字节,最重要的有三个:OriginalFirstThunk、Name、FirstThunk。一个IMAGE_IMPORT_DESCRIPTOR包含了从一个dll中导入的信息,在这个例子中我们有两个dll:user32.dll和kernel32.dll,最后用一个全0的IMAGE_IMPORT_DESCRIPTOR表示结束。我们只提取我们关心的东西:

OriginalFirstThunk1: 000020DC  -->  000006DC(物理地址)

Name1:                   0000220E  -->  0000080E

FirstThunk1:            00002010  -->  00000610

 

OriginalFirstThunk2: 000020CC  -->  000006CC(物理地址)

Name2:                   0000224C  -->  0000084C

FirstThunk2:            00002000  -->  00000600

 

我们先从简单的开始分析:Name

(前面的地址如00000200不是真实的物理地址,在stud_pe中此地址表示到.rdata的起始地址的偏移,也就是00000600+00000200,因此我们看到user32.dll的物理地址其实是0000080E,也就是Name1的值。kernel32.dll同理)

 

再继续往下之前,我们先要回到之前PE的import table结构中另外两个结构的定义:

  1. typedef struct _IMAGE_THUNK_DATA {  
  2.     union {  
  3.         ULONGLONG ForwarderString;  // PBYTE   
  4.         ULONGLONG Function;         // PDWORD  
  5.         ULONGLONG Ordinal;  
  6.         ULONGLONG AddressOfData;    // PIMAGE_IMPORT_BY_NAME  
  7.     } u1;  
  8. } IMAGE_THUNK_DATA;  
  9.   
  10. typedef struct _IMAGE_IMPORT_BY_NAME {  
  11.     WORD    Hint;  
  12.     BYTE    Name[1];  
  13. } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;  
typedef struct _IMAGE_THUNK_DATA { union { ULONGLONG ForwarderString; // PBYTE ULONGLONG Function; // PDWORD ULONGLONG Ordinal; ULONGLONG AddressOfData; // PIMAGE_IMPORT_BY_NAME } u1; } IMAGE_THUNK_DATA; typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; BYTE Name[1]; } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

我们看到IMAGE_THUNK_DATA是一个4字节的联合,在本例中其实就是一个IMAGE_IMPORT_BY_NAME的虚拟地址。如果导入函数不是用函数名导入的而是用序号导入的,情况就不一样了,具体是按名字导入还是按序号导入是看IMAGE_THUNK_DATA字段的最高位是否为1决定的。具体可查看PE文件结构。

 

接下来我们结合例子来看OriginalFirstThunk和FirstThunk两个字段表示的意义(以kernel32.dll为例):

OriginalFirstThunk: 000020CC  -->  000006CC 

                            内容如下:(28 22 00 00     1A 22 00 00     3C 22 00 00     00 00 00 00)

FirstThunk:  00002000  -->  00000600

                            内容如下:(28 22 00 00     1A 22 00 00     3C 22 00 00     00 00 00 00)

 

我们发现了两个地方:

1. 000006CC和00000600指向的内容是一样的,但是确存放了两个拷贝。

2. 00000600好熟悉啊。。。回头看看原来是data directory中IMAGE_DIRECTORY_ENTRY_IAT的地址!

 

OriginalFirstThunk和FirstThunk其实是指向了两个不同的表INT和IAT,但是这两个表其实内容是相同的。INT/IAT中的内容在本例中都是虚拟地址,至于该虚拟地址中的数据究竟表示了什么含义,就涉及IMAGE_IMPORT_BY_NAME这个结构体了。我们就取一个例子分析:1A 22 00 00  --> 0000081A(9B 00 45 78 69 74 50 72 6F 63 65 73 73 00)

Hint: 00 9B,这个是用来优化的,具体不展开,在本例中直接可以忽略。

Name: 45 78 69 74 50 72 6F 63 65 73 73 00 == ExitProcess

 

至于为什么需要用两个相同的表INT/IAT,原因比较复杂,简单的说是为了加快加载速度,能够进行链接时绑定。具体就不展开了,因为跟本文主题没有密切的联系,有兴趣的读者自己查阅相关资料。

 

对import table的分析基本全部完成了,现在我们再回头来看一下IAT的内容:

除了之前kernel32对应的FirstThunk,还有user32.dll对应的FirstThunk(00000610),也就是说IAT其实包含了该PE文件中所有导入的函数,以00 00 00 00作为一个dll的结尾。IAT对应的data directory的size告诉我们IAT的大小,在本例中50h表示这个IAT总共包含了80字节(20项)。

 

 

说到这里其实关于import table跟IAT相关的分析已经结束了,但是既然话题是import相关的,那我们也来分析一下在代码中调用一个输入函数是如何处理的:

比如:invoke ExitProcess,NULL

 

我们在OD中可以看到对应的机器/汇编指令:E8 5B 00 00 00/CALL 004011CA

004011CA这个地址是做什么用的呢?在OD中我们看到004011CA地址对应的指令如下:

JMP DWORD PTR DS:[402004]

那么402004是什么呢???噢!其实就是文件中对应的物理地址604!402004-400000(exe加载地址)== 2004 -->  604,也就是IAT表中的第二项,那么是1A 22 00 00么?不是!为什么叫IAT(image address table)?因为这个表的内容在加载时会用实在的导入函数地址填充。所以这个时候其实是真正的ExitProcess的地址了,我们可以用OD看到,运行时这个地址是76 0E 73 4E!

 

其实这个就是调用外部函数通常的做法,先用CALL指令转向一个跳转表:

而这个跳转表其实就是用JMP指令跳转到了IAT中的每一项。但是这个跳转表不是必需的,又是也可以直接用CALL DWORD PTR DS:[XXX]这样的语句实现外部函数的调用,XXX正是IAT中的某一项的地址。具体什么时候系统会创建这个跳转表是个疑问,暂时没有找到规律。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值