分享

函数调用之谜

 quasiceo 2013-12-05
分类: 开发与调试 2011-05-23 09:54 471人阅读 评论(1) 收藏 举报

最初发布在QQ空间: http://user.qzone.qq.com/31731705/blog/1305821803

函数指针的值不是函数地址? 介绍了内部的函数是如何通过ILT调用的,这次说说那些使用DLL import 进来的函数又是怎样调用的?

  1. typedef HANDLE (FAR WINAPI *PLL)( LPCWSTR );  
  2.   
  3. HANDLE WINAPI TestFunctionPointer( PLL pll, LPCWSTR wstr )  
  4. {  
  5.  HANDLE h = (*pll)(wstr);  
  6.  return h;  
  7. }  
  8.   
  9. void TestImportFunction()  
  10. {  
  11.  static const wchar_t dll[] = L"kernel32";  
  12.   
  13.  HANDLE (FAR WINAPI *pll)( LPCWSTR ) = 0;  
  14.  HANDLE (WINAPI *pTest)( PLL, LPCWSTR ) = 0;  
  15.  DWORD_PTR pfun = 0;  
  16.   
  17.  // call import function directly  
  18.  HANDLE h = LoadLibraryW( dll );  
  19.  FreeLibrary( h );  
  20.   
  21.  // call my function directly  
  22.  h = TestFunctionPointer( LoadLibraryW, dll );  
  23.  FreeLibrary( h );  
  24.   
  25.  // call my function by pointer  
  26.  pTest = TestFunctionPointer;  
  27.  h = (*pTest)( LoadLibraryW, dll );  
  28.  FreeLibrary( h );  
  29.   
  30.  // call import funciton by GetProcAddress  
  31.   
  32.  pll = GetProcAddress( GetModuleHandleW( dll ), "LoadLibraryW" );  
  33.  h = (*pll)( dll );  
  34.   
  35.  //DWORD_PTR  
  36.  pfun = TerminateProcess;  
  37.  pfun = GetProcAddress( h, "TerminateProcess" );  
  38.   
  39.  FreeLibrary( h );  
  40. }  

写了上述代码进行观察,首先看看最开始的2个函数调用,

00411d83 8bf4            mov     esi,esp
00411d85 686c6b4100      push    offset testC!dll (00416b6c)
00411d8a ff15eca14100    call    dword ptr [testC!_imp__LoadLibraryW (0041a1ec)]
00411d90 3bf4            cmp     esi,esp
00411d92 e808f4ffff      call    testC!ILT+410(__RTC_CheckEsp) (0041119f)
00411d97 8945d4          mov     dword ptr [ebp-2Ch],eax
00411d9a 8bf4            mov     esi,esp
00411d9c 8b45d4          mov     eax,dword ptr [ebp-2Ch]
00411d9f 50              push    eax
00411da0 ff15e8a14100    call    dword ptr [testC!_imp__FreeLibrary (0041a1e8)]
00411da6 3bf4            cmp     esi,esp
00411da8 e8f2f3ffff      call    testC!ILT+410(__RTC_CheckEsp) (0041119f)

最关键的是2条call指令,取一条分析一下call指令的目标,

0:000> ln 0041a1ec
(0041a1ec)   testC!_imp__LoadLibraryW   |  (0041a1f0)   testC!_imp__GetModuleFileNameW
Exact matches:
0:000> dd 0041a1ec l1
0041a1ec  77153c01
0:000> ln 77153c01
(77153c01)   kernel32!LoadLibraryW   |  (77153c1b)   kernel32!GetEnvironmentVariableW
Exact matches:
    kernel32!LoadLibraryW = <no type information>

可见,[testC!_imp__LoadLibraryW (0041a1ec)] dword ptr [testC!_imp__FreeLibrary (0041a1e8)]实际上都是位于.idata节的地址,他们的内容则是LoadLibraryWFreeLibrary真实的函数地址。

接下来是一个内部的函数,直接调用和通过函数指针指针调用,

00411dad 686c6b4100      push    offset testC!dll (00416b6c)
00411db2 a1eca14100      mov     eax,dword ptr [testC!_imp__LoadLibraryW (0041a1ec)]
00411db7 50              push    eax
00411db8 e8b4f4ffff      call    testC!ILT+620(_TestFunctionPointer (00411271)
00411dbd 8945d4          mov     dword ptr [ebp-2Ch],eax
......
00411dd3 c745ec71124100  mov     dword ptr [ebp-14h],offset testC!ILT+620(_TestFunctionPointer (00411271)
00411dda 8bf4            mov     esi,esp
00411ddc 686c6b4100      push    offset testC!dll (00416b6c)
00411de1 a1eca14100      mov     eax,dword ptr [testC!_imp__LoadLibraryW (0041a1ec)]
00411de6 50              push    eax
00411de7 ff55ec          call    dword ptr [ebp-14h]
00411dea 3bf4            cmp     esi,esp
00411dec e8aef3ffff      call    testC!ILT+410(__RTC_CheckEsp) (0041119f)
......

正如 函数指针的值不是函数地址 文中所讲,函数TestFunctionPointer 的调用不管是直接的,还是通过函数指针间接调用,均使用了ILT。

需要注意的地方是在传递函数参数的过程中同样使用了.idata节中的数据( dword ptr [testC!_imp__LoadLibraryW (0041a1ec)] )。也就是说,对于import进来的函数,直接调用也好,指针形式调用也好,都使用访问了.idata节地址中的数据,.idata节相当于一个中转,保存对应的真实的函数地址。但.idata节本身的位置,显然是模块相关的。

不过,还存在第3种调用方式GetProcAddress,继续分析余下的代码,

00411e07 8bf4            mov     esi,esp
00411e09 68706c4100      push    offset testC!`string' (00416c70)
00411e0e 8bfc            mov     edi,esp
00411e10 686c6b4100      push    offset testC!dll (00416b6c)
00411e15 ff1548a24100   call    dword ptr [testC!KERNEL32_NULL_THUNK_DATA (0041a248)]
00411e1b 3bfc            cmp     edi,esp
00411e1d e87df3ffff      call    testC!ILT+410(__RTC_CheckEsp) (0041119f)
00411e22 50              push    eax
00411e23 ff15e4a14100    call    dword ptr [testC!_imp__GetProcAddress (0041a1e4)]
00411e29 3bf4            cmp     esi,esp
00411e2b e86ff3ffff      call    testC!ILT+410(__RTC_CheckEsp) (0041119f)
00411e30 8945f8          mov     dword ptr [ebp-8],eax
00411e33 8bf4            mov     esi,esp
00411e35 686c6b4100      push    offset testC!dll (00416b6c)
00411e3a ff55f8          call    dword ptr [ebp-8]
00411e3d 3bf4            cmp     esi,esp
00411e3f e85bf3ffff      call    testC!ILT+410(__RTC_CheckEsp) (0041119f)
00411e44 8945d4          mov     dword ptr [ebp-2Ch],eax

代码一开始的call是调用GetModuleHandleW, 因为是import的函数,所以调用还是从.idata中转的。

0:000> ln 0041a248
(0041a248)   testC!KERNEL32_NULL_THUNK_DATA   |  (0041a28c)   testC!_imp___CrtSetCheckCount
0:000> d 0041a248 l4
0041a248  7715374d 00000000 00000000 00000000
0:000> ln 7715374d
(7715374d)   kernel32!GetModuleHandleWStub   |  (7715375d)   kernel32!CreateThreadStub
Exact matches:
    kernel32!GetModuleHandleWStub = <no type information>

执行到后面,看一下返回值,

0:000> dd [ebp-8] l1
0012fe54  77153c01

0:000> dv
            pll = 0x77153c01

0:000> ln poi(pll)
(77153c01)   kernel32!LoadLibraryW   |  (77153c1b)   kernel32!GetEnvironmentVariableW
Exact matches:
    kernel32!LoadLibraryW = <no type information>

可见GetProcAddress得到的是函数的真正地址,而函数直接调用或通过指针调用均为.idata节中转的。

在整个过程中,还发现一些有趣的事情,

0:000> x testc!loadli*
00412d24 testC!LoadLibraryW = <no type information>
0:000> x testC!term*
00414d56 testC!terminate = <no type information>
00412d12 testC!TerminateProcess = <no type information>
0:000> u 412d12 l4
testC!TerminateProcess:
00412d12 ff25e0a14100    jmp     dword ptr [testC!_imp__TerminateProcess (0041a1e0)]
testC!GetProcAddress:
00412d18 ff25e4a14100    jmp     dword ptr [testC!_imp__GetProcAddress (0041a1e4)]
testC!FreeLibrary:
00412d1e ff25e8a14100    jmp     dword ptr [testC!_imp__FreeLibrary (0041a1e8)]
testC!LoadLibraryW:
00412d24 ff25eca14100    jmp     dword ptr [testC!_imp__LoadLibraryW (0041a1ec)]

也就是说,testC模块自身也实现了这些函数,虽然内容是一句简单的jmp指令。

我尝试使用一些命令发现谁在使用它们,可惜未果。

0:000> lm test
start    end        module name
00400000 0041c000   testC     Thu May 19 13:47:03 2011 (4DD4AED7)
0:000> s -d 400000 l1c000 412d12
0:000> s -d 400000 l1c000 412d24

后来发现,这样做法是徒劳的,因为x86指令的复杂性,简单的搜索目的地址不能达到我的目的。使用IDA观察了一下,结果竟然有了新发现,

.text:00412D24
.text:00412D24                          ; =============== S U B R O U T I N E =======================================
.text:00412D24
.text:00412D24                          ; Attributes: thunk
.text:00412D24
.text:00412D24                          ; HMODULE __stdcall LoadLibraryW(LPCWSTR lpLibFileName)
.text:00412D24                          _LoadLibraryW@4 proc near               ; CODE XREF: LoadLibraryW(x)j
.text:00412D24
.text:00412D24                          lpLibFileName   = dword ptr  4
.text:00412D24
.text:00412D24 000 FF 25 EC A1 41 00                    jmp     ds:__imp__LoadLibraryW@4 ; LoadLibraryW(x)
.text:00412D24                          _LoadLibraryW@4 endp
.text:00412D24
这段代码是有人使用的,使用它的代码竟然是,

.text:004110F5
.text:004110F5                          ; =============== S U B R O U T I N E =======================================
.text:004110F5
.text:004110F5                          ; Attributes: thunk
.text:004110F5
.text:004110F5                          ; HMODULE __stdcall LoadLibraryW(LPCWSTR lpLibFileName)
.text:004110F5                          j__LoadLibraryW@4 proc near
.text:004110F5 000 E9 2A 1C 00 00                       jmp     _LoadLibraryW@4 ; LoadLibraryW(x)
.text:004110F5                          j__LoadLibraryW@4 endp
.text:004110F5
是一段ILT代码,

0:000> ln 4110f5
(004110f5)   testC!ILT+240(_LoadLibraryW   |  (004110fa)   testC!ILT+245(_HeapSetInformation
Exact matches:
0:000> u 4110f5
testC!ILT+240(_LoadLibraryW:
004110f5 e92a1c0000      jmp     testC!LoadLibraryW (00412d24)
testC!ILT+245(_HeapSetInformation:
004110fa e98d3c0000      jmp     testC!HeapSetInformation (00414d8c)
......

编译器为这些函数也做了ILT,不过即使从IDA中,也看不到这些ILT被使用的痕迹。

最后,做个简单的总结,

  1. 直接调用或者通过指针间接的调用import的函数,本质上是通过访问.idata节中的数据来调用真正的函数的。但GetProcAddress确实可以得到真实的函数地址。
  2. 执行模块本身也定义了一些那些import的对应函数及其ILT,不过奇怪的是没有代码使用它们。

最最后,提下x86的指令相当复杂,如果你仔细观察了上面的汇编代码的话,你会发现,同样call指令,机器码是多种多样,从ff15, e8, ff55等,jmp指令一样,有ff25, e9等等,这篇不错的文章,破解x86指令的奥秘,可以帮助理解。

 

更多 0
0
0
查看评论
1楼 Viper 2011-07-20 13:37发表 [回复]
最后问题的答案了,见 http://user.qzone.qq.com/31731705/blog/1306146001

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多