(*1*):建立DLL工程。在第二步选1。即默认。
//这个dll工程只用来输出两个函数。别无他用。
添加文件dll.cpp:
文件内容如下:
#include"stdio.h"
void __declspec(dllexport) ExportOne( void )
{
printf("I am ExportOne!/n");
}
void __declspec(dllexport) ExportTwo( void )
{
printf("I am ExportTwo!/n");
}
编译运行产生dll.obj dll.dll.
[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]
也可这样建立:
//文件dll.cpp
#include"stdio.h"
//void __declspec(dllexport) ExportOne( void )
void ExportOne(void)
{
printf("I am ExportOne!/n");
}
//void __declspec(dllexport) ExportTwo( void )
void ExportTwo(void)
{
printf("I am ExportTwo!/n");
}
//文件dll.def
; dll.def : Declares the module parameters for the DLL.
LIBRARY "dll"
DESCRIPTION 'dll Windows Dynamic Link Library'
EXPORTS
; Explicit exports can go here
ExportOne @1
ExportTwo @2
[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]
(*2*):建立LIB工程。
//这个LIB工程只用来测试引入刚才DLL输出的两个函数。
添加文件lib.cpp
文件内容如下:
#include"stdio.h"
void ExportOne(void);
void ExportTwo(void);
void main()
{
ExportOne();
ExportTwo();
}
编译运行产生lib.obj lib.exe.
(*3*)LIB.OBJ分析
(*4*)反编译LIB.OBJ.注意代码节的文件偏移为00000392
:00000000 55 push ebp
......
:00000018 E800000000 call 0000001D //这里就是ExportOne()调用
:0000001D E800000000 call 00000022 //这里就是ExportTwo()调用
......
:00000032 C3 ret
(*5*)LIB.EXE分析:
:00401000 55 push ebp
......
:00401017 AB stosd
* Reference To: dll.ExportOne, Ord:0001h
|
:00401018 E81D000000 Call 0040103A
* Reference To: dll.ExportTwo, Ord:0002h
|
:0040101D E812000000 Call 00401034
......
:00401032 C3 ret
:00401033 CC int 03
* Referenced by a CALL at Address:
|:0040101D
|
* Reference To: dll.ExportTwo, Ord:0002h
|
:00401034 FF25C8C04000 Jmp dword ptr [0040C0C8]
* Referenced by a CALL at Address:
|:00401018
|
* Reference To: dll.ExportOne, Ord:0001h
|
:0040103A FF25C4C04000 Jmp dword ptr [0040C0C4]
(*6*)引入函数与非引入函数的区别。
从上我们可以看出,其实不管是不是引入函数,编译器产生的函数调用代码都是CALL XXXXXXXX形式的。
//from dll.lib
Archive member name at 8: /
3E951F55 time/date Thu Apr 10 15:37:57 2003
...
correct header end
7 public symbols
1FE __IMPORT_DESCRIPTOR_dll
4F8 __NULL_IMPORT_DESCRIPTOR
62C dll_NULL_THUNK_DATA
778 ?ExportOne@@YAXXZ
778 __imp_?ExportOne@@YAXXZ
7E2 ?ExportTwo@@YAXXZ
7E2 __imp_?ExportTwo@@YAXXZ
我们可以看到,在LIB文件中有引入函数的信息。
函数符号比如?ExportOne@@YAXXZ能够被解析。并且LIB文件中有很多关于引入函数的信息。比如:
Summary
BA .debug$S
14 .idata$2
14 .idata$3
4 .idata$4
4 .idata$5
8 .idata$6
所有的.idata节最终会被合并到可执行文件的.IDATA节中。从而形成IAT和其他有关引入表的结构。
SECTION HEADER #2
.idata$5 name
...
C0300040 flags
...
RAW DATA #2
00000000: 00 00 00 00
如果函数是通过序号引入的。那么在.idata$5节的DWORD的最高位为1。低位是引入(出)序号。
否则.idata$5节的DWORD为0。
如果函数是通过名字引入的。那么在.idata$6节的第一个WORD为引入(出)序号。接下去是一个函数名字。
**通过LIB文件,函数被决议为一个JMP DWORD PTR[XXXXXXXX]形式的指令。
通常称为STUB。当然LIB文件中也有引入函数的真正地址。
010 00000000 SECT3 notype () External | ?ExportOne@@YAXXZ (void __cdecl ExportOne(void))
//以下为函数ExportOne的代码。
SECTION HEADER #3
.text name
...
RAW DATA #3
00000000: 55 8B EC 83 EC 40 53 56 57 8D 7D C0 B9 10 00 00 U....@SVW.}.....
00000010: 00 B8 CC CC CC CC F3 AB 68 00 00 00 00 E8 00 00 ........h.......
00000020: 00 00 83 C4 04 5F 5E 5B 83 C4 40 3B EC E8 00 00 ....._^[..@;....
00000030: 00 00 8B E5 5D C3 ....].
综上所述,对引入函数。产生的代码大致形式如下:
CALL XXXXXXXX
...
XXXXXXXX:
JMP DWORD PTR[YYYYYYYY]
YYYYYYYY地址在引入节部分。
最后调到引入函数的地址去执行。