windows PE Image 文件分析(4)--- 导入函数的绑定



上一篇文章探讨了 helloworld.exe 映像的 import table 和 .idata 节的情况,现在接着看一看 windows 是如何使用导入的函数。

我重申一下我的环境:helloworld.exe 是以 Win32 debug 选项编译的,但是我的电脑是 64 位的 Win7 系统,所以 32 位的 helloworld.exe 运行在 64 位的 windows 下。

前面的文章已经提过:helloworld.exe 的 ImageBase 已经不是 0x00400000 ,在我的实验上是 0x011b0000,这个 ImageBase 可能每次都不一样的。

我不想再回头弄个 64 位的 helloworld.exe 再重新探索。:)

 

1. 在程序中调用 API

从我的 helloworld.cpp 示例程序中提取几条调用 API 语句:


1.1 语句 1

LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);

代码调用了 User32.dll 的 API 函数,那么 LoadString() 将作为 import 函数存在于 helloworld.exe 的 import table

这条语句,被编译为:

011C1490 6A 64                push        64h  
011C1492 68 00 72 1C 01       push        offset szTitle (11C7200h)  
011C1497 6A 67                push        67h  
011C1499 8B 45 08             mov         eax,dword ptr [hInstance]  
011C149C 50                   push        eax  
011C149D FF 15 C0 83 1C 01    call        dword ptr [__imp__LoadStringW@16 (11C83C0h)]  
011C14A3 3B F4                cmp         esi,esp 

这条 call 指令里从地址 [0x011c83c0] 取出 IP 来执行, 这个地址里面装的就是 LoadStringW() 的真实地址。


1.2 语句 2

wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_HELLOWORLD));

代码调用了 User32.dll 的 API 函数:LoadIcon(),这条语句被编译为:

011C1647 8B F4                mov         esi,esp  
011C1649 6A 6B                push        6Bh  
011C164B 8B 45 08             mov         eax,dword ptr [hInstance]  
011C164E 50                   push        eax  
011C164F FF 15 B4 83 1C 01    call        dword ptr [__imp__LoadIconW@8 (11C83B4h)]  
011C1655 3B F4                cmp         esi,esp 

地址 [0x011C83B4] 放在 LoadIcon() 函数的真实地址。


1.3 语句 3

wcex.hCursor = LoadCursor(NULL, IDC_ARROW);

代码调用了 User32.dll 的 API 函数:LoadCursor(),这条语句被编译为:

011C1661 68 00 7F 00 00       push        7F00h  
011C1666 6A 00                push        0  
011C1668 FF 15 B8 83 1C 01    call        dword ptr [__imp__LoadCursorW@8 (11C83B8h)]  
011C166E 3B F4                cmp         esi,esp 

地址 [0x011C83B8] 放在 LoadCursor() 函数的真实地址。

 

2. helloworld.exe 的 import table

现在来看一看 helloworld.exe 的 import table

OriginalFirstThunk    hint/name RVA
----------------------------------
011C81AC              3E 85 01 00
011C81B0              2C 85 01 00 
011C81B4              1C 85 01 00 
011C81B8              0A 85 01 00 
011C81BC              FC 84 01 00 
011C81C0              F0 84 01 00 
011C81C4              DE 84 01 00 
011C81C8              CC 84 01 00 
011C81CC              BE 84 01 00 
011C81D0              AE 84 01 00 
011C81D4              A2 84 01 00     // LoadIcon() 的 Hint/Name 的 RVA
011C81D8              94 84 01 00     // LoadCursor() 的 Hint/Name 的 RVA
011C81DC              80 84 01 00
011C81E0              72 84 01 00     // LoadString() 的 Hint/Name 的 RVA
011C81E4              5E 84 01 00 
011C81E8              50 84 01 00 
011C81EC              38 84 01 00
011C81F0              24 84 01 00 
011C81F4              10 84 01 00 
011C81F8              2C 89 01 00 
011C81FC              3C 89 01 00

上面是前面提到过的一组由 OriginalFirstThunk 指向的 import lookup table,所不同的是:我已经将基于 0x00400000 的地址值转化为基于 0x011b0000 的地址值。

注意上面红色粗体标注的是 LoadString() 函数的 Hint/Name table 的 RVA 值。

下面再来看一看一组由 FirstThunk 引出的 Import Address Table:

FirstThunk         Hint/Name table RVA
----------------------------------------
011C838C              3E 85 01 00
011C8390              2C 85 01 00 
011C8394              1C 85 01 00 
011C8398              0A 85 01 00 
011C839C              FC 84 01 00
011C83A0              F0 84 01 00 
011C83A4              DE 84 01 00 
011C83A8              CC 84 01 00 
011C83AC              BE 84 01 00
011C83B0              AE 84 01 00 
011C83B4              A2 84 01 00     // LoadIcon() 的 Hint/Name 的 RVA
011C83B8              94 84 01 00     // LoadCursor() 的 Hint/Name 的 RVA 
011C83BC              80 84 01 00
011C83C0              72 84 01 00     // LoadString() 的 Hint/Name 的 RVA
011C83C4              5E 84 01 00 
011C83C8              50 84 01 00 
011C83CC              38 84 01 00
011C83D0              24 84 01 00 
011C83D4              10 84 01 00 
011C83D8              2C 89 01 00 
011C83DC              3C 89 01 00

这也是前面提到过的由 FirstThunk 得出的 Import Address Table,同样地址值已经转化为基于 0x011b0000 的地址值

上面的 Import Lookup Table 和 Import Address Table 红色标注的是一一对应的,下面是其一个函数的情况:

  • 由 OriginalFirstThunk 值 0x011C81E0 指向的 Hint/Name Table 的 RVA:0x00018472
  • 由 FirstThunk 值 0x011C83C0 指向的 Hint/Name Table 的 RVA:0x00018472

由 OriginalFirstThunk 得到 Hint/Name table 和 FirstThunk 得到 Hint/Name table 存在着差异,虽然在函数绑定之前是相同的。 由 OriginalFirstThunk 得到 Hint/Name 这个途径是不变的。

但是由 FirstThunk 得到 Hint/Name table 这个途径在函数绑定之后,发生了改变。FirstThunk 得到的是函数的真实地址,而不是 Hint/Name table

 

3. 函数的绑定

回到指令1来:

011C149D FF 15 C0 83 1C 01 call dword ptr [__imp__LoadStringW@16 (11C83C0h)]

这个地址 0x011C83C0 就是上面所例了 FirstThunk 值,此时它被绑定为 LoadStringW() 函数的真实地址:

76E67C12 8B FF                mov         edi,edi  
76E67C14 55                   push        ebp  
76E67C15 8B EC                mov         ebp,esp  
76E67C17 6A 00                push        0  
76E67C19 FF 75 14             push        dword ptr [ebp+14h]  
76E67C1C FF 75 10             push        dword ptr [ebp+10h]  
76E67C1F FF 75 0C             push        dword ptr [ebp+0Ch]  
76E67C22 FF 75 08             push        dword ptr [ebp+8]  
76E67C25 FF 15 E0 00 ED 76    call        dword ptr ds:[76ED00E0h]  
76E67C2B 5D                   pop         ebp  
76E67C2C C2 10 00             ret         10h 

上面这段是 LoadString() 函数的代码,入口地址是:0x76E67C12

指令2:

011C164F FF 15 B4 83 1C 01    call        dword ptr [__imp__LoadIconW@8 (11C83B4h)]

函数 LoadIconW() 的真实地址是:

76E69EAF 8B FF                mov         edi,edi  
76E69EB1 55                   push        ebp  
76E69EB2 8B EC                mov         ebp,esp  
76E69EB4 68 40 80 00 00       push        8040h  
76E69EB9 6A 00                push        0  
76E69EBB 6A 00                push        0  
76E69EBD 6A 03                push        3  
76E69EBF FF 75 0C             push        dword ptr [ebp+0Ch]  
76E69EC2 FF 75 08             push        dword ptr [ebp+8]  
76E69EC5 E8 35 E9 FF FF       call        76E687FF  
76E69ECA 5D                   pop         ebp  
76E69ECB C2 08 00             ret         8 

指令3:

011C1668 FF 15 B8 83 1C 01    call        dword ptr [__imp__LoadCursorW@8 (11C83B8h)]

函数 LoadCursor() 的真实地址是:

76E689EE 8B FF                mov         edi,edi  
76E689F0 55                   push        ebp  
76E689F1 8B EC                mov         ebp,esp  
76E689F3 68 40 80 00 00       push        8040h  
76E689F8 6A 00                push        0  
76E689FA 6A 00                push        0  
76E689FC 6A 01                push        1  
76E689FE FF 75 0C             push        dword ptr [ebp+0Ch]  
76E68A01 FF 75 08             push        dword ptr [ebp+8]  
76E68A04 E8 F6 FD FF FF       call        76E687FF  
76E68A09 5D                   pop         ebp  
76E68A0A C2 08 00             ret         8 

下面看看 FirstThunk 经过绑定后:

Import Address Table        绑定前          绑定后
----------------------------------------------------
0x011C83B4              0x000184A2       0x76E69EAF
0x011C83B8              0x00018494       0x76E689EE
0x011C83C0              0x00018472       0x76E67C12

上面所示,由 FirstThunk 指向的 Import Address Table 已经是导入函数的真实地址。

 

4. 绑定过程

经过绑定后的 Import Address Table 已经是被改变, 但是 OriginalFirstThunk 指向的 Import Lookup Table 是保持不变的。

函数的绑定过程很容易推出:先以 Hint/Name table 中的 Hint 在 DLL export table 进行索引,找到匹配的函数后,然后从 FirstThunk 找到 Import Address Table,用找到的匹配函数入口地址来改写 Import Address table 相应的表项。

这个绑定过程只是我简单的推论,未经过深入探索,并不能作准。:)


版权 mik 所有,转载请注明出处

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值