by Iczelion (翻译:花心萝卜 yqzq@163.net) 9.5.2000
这篇短文是讲述关于建立 MASM 导入库(import libraries)技巧,我假设你已经知道什么是导入库。在下面,我将集中讲述建立 MASM 导入库的方法。
MASM 导入库的格式:
MASM 和 VC++ 可以使用相同的导入库,MS 导入库使用不同于 TASM 的 OMF 格式的变更的 COFF 文件格式,这就是为什么 TASM 和 MASM 的导入库不能互用的原因,我将不详细介绍有关 MS 导入库的格式。可以这样说,每一个 MS 导入库都包含某个 DLL 中函数的信息(你将要用这些信息来调用 DLL 中的函数),这些信息包括函数名和它所有参数的尺寸。如果你用一个文本编辑器打开 kernel32.lib,你回发现一些如下格式的信息:
_ExitProcess@4
_CreateProcessA@40
函数名被装饰上了一个“_”,在“@”之后的数字表示了该函数所有参数的尺寸(字节为单位),ExitProcess 函数只有一个 DWORD 的参数,所以后面的数字是 4。 LIB 中为什么要包含这些参数尺寸的信息呢?当你用 INVOKE 调用函数时,这些信息被用来检测传递给函数的参数是否正确。如果你使用“手工”将参数压入堆栈,并通过“CALL”来调用函数的话,MASM 将无法检测参数是否正确。这将导致我们几乎没有办法建立一个 DLL 的导入库,因为 DLL 并不包含清楚的关于参数尺寸的信息。
从 DLL 建立 MASM 导入库
如果你很乐意用“手动”(CALL)的方法去调用函数的话,你可以象下面这样为任何一个 DLL 建立 MASM 的导入库:
使用 dumpbin.exe,它可以导出 DLL 输出(EXPORT)函数的名字。
Dumpbin /EXPORTS blah.dll > output.txt
在你获得了函数名列表之后,通过他们建立一个模块定义文件(.DEF)。 举个例子:如果 DLL 只包含一个函数:GetSomeLine 在一个文本文件中输入如下内容:
LIBRARY blah
EXPORTS
GetSomeLine
并将其保存为“blah.def
象这样,运行 lib.exe,通过模块定义文件建立一个导入库:
lib /DEF:blah.def
就是它了!你将获得 blah.lib,只要你不使用 INVOKE 调用函数的话,你就可以在 MASM 中使用它。
建立通过 INVOKE 调用函数的 MASM 导入库:
我并不反对你使用上面的方法,但 INVOKE 确实是一个调用函数的好途径。这也是我较 TASM 更喜欢 MASM 的原因之一。但就象我早先强调的,我们几乎不可能从一个 DLL 建立一个能 100% 工作的 MASM 导入库。如果你使用 INVOKE,你将不能用上面的方法建立一个 MASM 导入库。举个例子,你可以想象如果你在.DEF 文件中修改了函数的“@XX”部分,导入库将仍然正常建立,但请相信我,他不会工作的。 建立一个可以使用 INVOKE 的导入库的一个简单的方法是使用 MASM。如果你写过 DLL 的代码,你会发现你不仅的到了一个 DLL,而且还得到了一个导入库,没错,它就是我们要得! 我们的策略是:
- 获得函数名和所有参数的尺寸
- 建立一个包含正确个数和尺寸的 DLL 源代码
- 建立一个描述 ASM 源代码中相应函数的模块定义文件(.DEF)
- 将源代码按 DLL 汇编
你将获得一个功能完全的 MASM 导入库,上面的步骤应做更多的说明
获得函数名和所有参数尺寸
这是我们处理过程中最困难的部分了。如果你仅仅只有 DLL,你将经历无意义的冒险。下面是我所能想出的方法,不过没有一个能 100% 工作。
使用交互式反编译工具(Interactive Disassembler (IDA))反编译 DLL,通过这个奇妙的工具,你可以获得函数参数的大概尺寸,但这些信息是不完全的,IDA 是一个功能强大的工具,不过有时必须靠我们自己判断什么是什么。你将不得不仔细分析反编译后的结果。
观察堆栈指针在调用函数之前和之后的值。方法如下:
- 通过 GetProcAddress 获得函数的地址。
- 调用想要测试的每一个函数,但请注意,调用这些函数时,不要给他们传递任何的参数。调用前请注意 ESP 的值。
- 当函数返回后,比较调用函数前、后 ESP 的值。基本原理是:stdcall 参数调用协定规定,函数自己负责恢复堆栈,现在知道为什么我们要不传递任何参数了吧,我们没传递参数,而函数却自作聪明“恢复”了 ESP 指针,所以 ESP 的变化值就是我们要得参数尺寸了。
不过,上面的方法并不是万无一失的,下面的这些情况将会导致失败:
- 如果 DLL 中的函数使用了不同于 stdcall 的别的参数传递协定。
- 如果函数在恢复堆栈时失败,我们将无法得到 ESP 的正确值。
- 如果这个函数的作用是去做一些危险的事情,比如硬盘格式化,那我们即使得到了 ESP,恐怕代价大了点
研究现有的使用 DLL 的程序,你可以通过调试/反编译这些程序去获得函数参数的个数和尺寸。不论如何,只要有函数在 DLL 中,而又没有任何程序调用过它,你可以用上面的两个方法。
建立我们自己的 DLL
在你获得了函数的名字和参数尺寸后,你可以建立一个 DLL 框架并在框架中添加和其他 DLL、文件中的相同名称的函数。举个例子,如果 DLL 只含有一个函数:GetSomeLine.它有 16BYTES 的参数。在 ASM 文件中,你可以这样写:
.386
.model flat,stdcall
.code
GetSomeLine proc param1:DWORD, param2:DWORD, param3:DWORD, param4:DWORD
GetSomeline endp
end
你可能要问,“这是什么?”。一个没有处理部分的程序?请记住:一个导入库并没有记录一个函数是如何实现的,它只是记录函数名和参数尺寸而已,它的任务就是提供函数的名称和尺寸。所以我们不需要添加函数的处理部分。当我们建立 DLL 时,MASM 会帮我们完成它的导入库的建立。 MASM 在建立导入库时并不关心每个具体参数的尺寸,它总是象下面这样:
.386
.model flat,stdcall
.code
GetSomeLine proc param1:BYTE, param2:BYTE, param3:BYTE, param4:BYTE
GetSomeline endp
end
然后 MASM 将在导入库中建立_GetSomeLine@16(它会把每一个参数看作 DWORD),而并不管它的参数是 4 个 BYTE 还是 DWORD 或是其他什么
建立匹配的模块定义文件(.DEF)
这是一个简单的工作,你需要这个文件来指导 MASM 去建立正确的 DLL 和与之匹配的导入库。一个模块定义文件模板如下:
LIBRARY <The name of the DLL>
EXPORTS
<The names of the functions>
你仅仅需要填入 DLL 的名字,然后在 EXPORTS 下添入函数的名字。每个函数名一行。保存文件,你将获得一个模块定义文件。
汇编 DLL 源代码
最后一步也是最简单的一步,仅仅需要 ML.EXE 和 LINK.EXE
ml /c /coff /Cp blah.asm
link /DLL /NOENTRY /def:blah.def /subsystem:windows blah.obj
好了,查看一下你的项目目录,你会发现你想要的导入库和 DLL。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于