最初发布在QQ空间: http://user.qzone.qq.com/31731705/blog/1305821803
函数指针的值不是函数地址? 介绍了内部的函数是如何通过ILT调用的,这次说说那些使用DLL import 进来的函数又是怎样调用的?
typedef HANDLE (FAR WINAPI *PLL)( LPCWSTR ); HANDLE WINAPI TestFunctionPointer( PLL pll, LPCWSTR wstr ) { HANDLE h = (*pll)(wstr); return h; } void TestImportFunction() { static const wchar_t dll[] = L"kernel32"; HANDLE (FAR WINAPI *pll)( LPCWSTR ) = 0; HANDLE (WINAPI *pTest)( PLL, LPCWSTR ) = 0; DWORD_PTR pfun = 0; // call import function directly HANDLE h = LoadLibraryW( dll ); FreeLibrary( h ); // call my function directly h = TestFunctionPointer( LoadLibraryW, dll ); FreeLibrary( h ); // call my function by pointer pTest = TestFunctionPointer; h = (*pTest)( LoadLibraryW, dll ); FreeLibrary( h ); // call import funciton by GetProcAddress pll = GetProcAddress( GetModuleHandleW( dll ), "LoadLibraryW" ); h = (*pll)( dll ); //DWORD_PTR pfun = TerminateProcess; pfun = GetProcAddress( h, "TerminateProcess" ); FreeLibrary( h ); }
写了上述代码进行观察,首先看看最开始的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节的地址,他们的内容则是LoadLibraryW和FreeLibrary真实的函数地址。
接下来是一个内部的函数,直接调用和通过函数指针指针调用,
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被使用的痕迹。
最后,做个简单的总结,
最最后,提下x86的指令相当复杂,如果你仔细观察了上面的汇编代码的话,你会发现,同样call指令,机器码是多种多样,从ff15, e8, ff55等,jmp指令一样,有ff25, e9等等,这篇不错的文章,破解x86指令的奥秘,可以帮助理解。