65.9K
CodeProject 正在变化。 阅读更多。
Home

导入库文件(.lib)的结构

starIconstarIconstarIconstarIconstarIcon

5.00/5 (12投票s)

2018年7月28日

CPOL

6分钟阅读

viewsIcon

30933

downloadIcon

297

本文展示了与头文件一起用于链接 DLL 的导入库文件(.lib)的结构。

引言

您是否曾经好奇过 Microsoft 的 import 库文件的内容?本文简要介绍了 .lib 文件结构,并提供了根据 export module.dll.sys.exe 文件)的名称和其导出的函数列表(函数可以被挑选,所以不一定需要提供完整列表)来创建导入库的源代码。

关于调用约定和名称修饰的几句话

在 x86 上,我们可以为 C function 使用以下调用约定:

  1. __cdecl
  2. __stdcall
  3. __fastcall
  4. __vectorcall

在 x64 上,我们可以为 C function 使用以下调用约定(编译器会忽略 __cdecl__stdcall__fastcall):

  1. 默认(函数名称不被修饰)
  2. __vectorcall

有关名称修饰的详细信息,请参阅此链接

导入库结构

导入库是一个归档文件,它以 arch signature 开头,这是一个 8 字节的 字符串,即:

!<arch>\n

签名之后,我们有一堆文件。

每个文件都以固定长度的头部开始,后面是任意长度的体部(头部包含体部的长度)。

现在我们来考虑 import 库“托管”的 symbols。假设相关的 export moduleBOOTVID.DLL,我们将有三个“预定义”的 symbols

  1. __IMPORT_DESCRIPTOR_BOOTVID
  2. __NULL_IMPORT_DESCRIPTOR
  3. <7Fh> BOOTVID_NULL_THUNK_DATA // string0x7F 开头

对于每个导入的函数,我们将有两个额外的符号。让我们考虑这个 C function

void __stdcall VidDisplayString(char *pString);

import 库的 x86 版本中,我们将有:

  1. _VidDisplayString@4 // 修饰后的函数名
  2. __imp__VidDisplayString@4 // 与 string 相同,前缀为 __imp_

import 库的 x64 版本中,我们将有:

  1. VidDisplayString
  2. __imp_VidDisplayString // 与 string 相同,前缀为 __imp_

现在我们来描述每个文件的内容。归档中的前两个文件包含所有符号名称以及指向其他文件的偏移量。然后,我们有三个文件专门用于三个“预定义”符号。最后,我们有专门用于函数的(每个函数一个文件)文件。

文件 1 包含 符号数量 字段、符号名称及其对应的偏移量(相对于归档开头的其他文件偏移量)。因此,文件 3 专用于 __IMPORT_DESCRIPTOR_BOOTVID文件 4 专用于 __NULL_IMPORT_DESCRIPTOR,依此类推。请注意,尽管一个函数会引入两个符号,但每个函数只专用于一个文件。

文件 2 包含偏移量表、符号名称及其对应的索引(指向偏移量表的索引)。它本质上是以不同形式给出的相同信息。请注意,索引从 1 开始。

文件 3、4、5 分别专用于 __IMPORT_DESCRIPTOR_BOOTVID__NULL_IMPORT_DESCRIPTOR<7F> BOOTVID_NULL_THUNK_DATA。我不会详细展示它们,因为它们对于所有 import 库来说基本相同。相反,我想重点关注 文件 6 并详细展示它。让我们看看表示文件头部的结构。

struct FILE_HEADER                // size = 0x3C
{
    char Part1[16];
    char Id[24];
    char Part3[8];
    char BodyLength[10];
    char Part5[2];
};

Id 字段包含由 time 函数生成的随机数(以 ASCII 形式),BodyLength 字段包含后续文件体部的长度(以 ASCII 形式)。文件头部之后,是符号描述符。

struct SYMBOL_DESCRIPTOR          // size = 0x14
{
    WORD a;
    WORD b;
    WORD c;
    WORD Architecture;
    DWORD Id;
    DWORD Length;
    union
    {
        WORD Hint;
        WORD Ordinal;
        WORD Value;
    }
    WORD Type;
};

Architecture 字段包含 x86 的 0x14C 和 x64 的 0x8664Id 字段是以二进制形式表示的相同随机数。Hint / Ordinal 字段包含函数提示/序号。类型该字段指定 import 类型(稍后会找到描述)。Length 字段包含后面两个 string(包括它们的 null 字符)的总长度。

导入/导出场景

我们将考虑三种可能的场景:

  1. 我们编译 export module 并导出函数,而不使用模块定义文件。

    输入

    来源

    __declspec(dllexport) void __cdecl function1() {}
    
    __declspec(dllexport) void __stdcall function2() {}
    
    __declspec(dllexport) void __fastcall function3() {}
    
    __declspec(dllexport) void __vectorcall function4() {}

    x86 输出

    导入库 (SYMBOL_DESCRIPTOR)

    Name = _function1, Hint = 0, Type = 8
    
    Name = _function2@0, Hint = 1, Type = 4
    
    Name = @function3@0, Hint = 2, Type = 4
    
    Name = function4@@0, Hint = 3, Type = 4

    导入库存储:提示(从 0 开始,表示 AddressOfNames 数组的索引)。

    Export module 的导出 (AddressOfNames, AddressOfNameOrdinals)

    Name = _function1, Ordinal = index into AddressOfFunctions array
    
    Name = _function2@0, Ordinal = index into AddressOfFunctions array
    
    Name = @function3@0, Ordinal = index into AddressOfFunctions array
    
    Name = function4@@0, Ordinal = index into AddressOfFunctions array

    尽管名称为 Ordinal,但 AddressOfNameOrdinals 存储的是 Index (从 0 开始,表示 AddressOfFunctions 数组的索引)。

    Import module 的导入 (IMAGE_IMPORT_BY_NAME)

    Name = _function1, Hint = 0
    
    Name = _function2@0, Hint = 1
    
    Name = @function3@0, Hint = 2
    
    Name = function4@@0, Hint = 3

    x64 输出

    导入库 (SYMBOL_DESCRIPTOR)

    Name = function1, Hint = 0, Type = 4
    
    Name = function2, Hint = 1, Type = 4
    
    Name = function3, Hint = 2, Type = 4
    
    Name = function4@@0, Hint = 3, Type = 4

    导入库存储:提示(从 0 开始,表示 AddressOfNames 数组的索引)。

    Export module 的导出 (AddressOfNames, AddressOfNameOrdinals)

    Name = function1, Ordinal = index into AddressOfFunctions array
    
    Name = function2, Ordinal = index into AddressOfFunctions array
    
    Name = function3, Ordinal = index into AddressOfFunctions array
    
    Name = function4@@0, Ordinal = index into AddressOfFunctions array

    尽管名称为 Ordinal,但 AddressOfNameOrdinals 存储的是 Index (从 0 开始,表示 AddressOfFunctions 数组的索引)。

    Import module 的导入 (IMAGE_IMPORT_BY_NAME)

    Name = function1, Hint = 0
    
    Name = function2, Hint = 1
    
    Name = function3, Hint = 2
    
    Name = function4@@0, Hint = 3
  2. 我们编译一个 DLL,并使用模块定义文件导出函数(我们指定函数名)。

    输入

    来源

    void __cdecl function1() {}
    
    void __stdcall function2() {}
    
    void __fastcall function3() {}
    
    void __vectorcall function4() {}

    Def 文件

    EXPORTS
    
    function1
    
    function2
    
    function3
    
    function4

    x86 输出

    导入库 (SYMBOL_DESCRIPTOR)

    Name = _function1, Hint = 0, Type = 8
    
    Name = _function2@0, Hint = 1, Type = 0xC
    
    Name = @function3@0, Hint = 2, Type = 0xC
    
    Name = function4@@0, Hint = 3, Type = 0xC

    导入库存储:提示(从 0 开始,表示 AddressOfNames 数组的索引)。

    Export module 的导出 (AddressOfNames, AddressOfNameOrdinals)

    // function names are not decorated
    
    Name = function1, Ordinal = index into AddressOfFunctions array
    
    Name = function2, Ordinal = index into AddressOfFunctions array
    
    Name = function3, Ordinal = index into AddressOfFunctions array
    
    Name = function4, Ordinal = index into AddressOfFunctions array

    尽管名称为 Ordinal,但 AddressOfNameOrdinals 存储的是 Index (从 0 开始,表示 AddressOfFunctions 数组的索引)。

    Import module 的导入 (IMAGE_IMPORT_BY_NAME)

    // function names are not decorated
    
    Name = function1, Hint = 0
    
    Name = function2, Hint = 1
    
    Name = function3, Hint = 2
    
    Name = function4, Hint = 3

    x64 输出

    导入库 (SYMBOL_DESCRIPTOR)

    Name = function1, Hint = 0, Type = 4
    
    Name = function2, Hint = 1, Type = 4
    
    Name = function3, Hint = 2, Type = 4
    
    Name = function4@@0, Hint = 3, Type = 0xC

    导入库存储:提示(从 0 开始,表示 AddressOfNames 数组的索引)。

    Export module 的导出 (AddressOfNames, AddressOfNameOrdinals)

    // function names are not decorated
    
    Name = function1, Ordinal = index into AddressOfFunctions array
    
    Name = function2, Ordinal = index into AddressOfFunctions array
    
    Name = function3, Ordinal = index into AddressOfFunctions array
    
    Name = function4, Ordinal = index into AddressOfFunctions array

    尽管名称为 Ordinal,但 AddressOfNameOrdinals 存储的是 Index (从 0 开始,表示 AddressOfFunctions 数组的索引)。

    Import module 的导入 (IMAGE_IMPORT_BY_NAME)

    // function names are not decorated
    
    Name = function1, Hint = 0
    
    Name = function2, Hint = 1
    
    Name = function3, Hint = 2
    
    Name = function4, Hint = 3
  3. 我们编译一个 DLL,并使用模块定义文件导出函数(我们指定函数名和序号)。

    输入

    来源

    void __cdecl function1() {}
    
    void __stdcall function2() {}
    
    void __fastcall function3() {}
    
    void __vectorcall function4() {}

    Def 文件

    EXPORTS
    
    function1 @1
    
    function2 @2
    
    function3 @3
    
    function4 @4

    x86 输出

    导入库 (SYMBOL_DESCRIPTOR)

    Name = _function1, Ordinal = 1, Type = 0
    
    Name = _function2@0, Ordinal = 2, Type = 0
    
    Name = @function3@0, Ordinal = 3, Type = 0
    
    Name = function4@@0, Ordinal = 4, Type = 0

    导入库存储 Ordinal (从 1 开始,表示备用函数名)。

    Export module 的导出 (AddressOfNames, AddressOfNameOrdinals)

    // function names are not decorated
    
    Name = function1, Ordinal = index into AddressOfFunctions array
    
    Name = function2, Ordinal = index into AddressOfFunctions array
    
    Name = function3, Ordinal = index into AddressOfFunctions array
    
    Name = function4, Ordinal = index into AddressOfFunctions array

    尽管名称为 Ordinal,但 AddressOfNameOrdinals 存储的是 Index (从 0 开始,表示 AddressOfFunctions 数组的索引)。如果我们为某个函数使用了 NONAME 指令,那么它在 AddressOfNamesAddressOfNameOrdinals 数组中将没有对应的条目。

    Import module 的导入(现在我们有 Ordinal 而不是 IMAGE_IMPORT_BY_NAME

    Ordinal = 1
    
    Ordinal = 2
    
    Ordinal = 3
    
    Ordinal = 4

    x64 输出

    导入库 (SYMBOL_DESCRIPTOR)

    Name = function1, Ordinal = 1, Type = 0
    
    Name = function2, Ordinal = 2, Type = 0
    
    Name = function3, Ordinal = 3, Type = 0
    
    Name = function4@@0, Ordinal = 4, Type = 0

    导入库存储 Ordinal (从 1 开始,表示备用函数名)。

    Export module 的导出 (AddressOfNames, AddressOfNameOrdinals)

    // function names are not decorated
    
    Name = function1, Ordinal = index into AddressOfFunctions array
    
    Name = function2, Ordinal = index into AddressOfFunctions array
    
    Name = function3, Ordinal = index into AddressOfFunctions array
    
    Name = function4, Ordinal = index into AddressOfFunctions array

    尽管名称为 Ordinal,但 AddressOfNameOrdinals 存储的是 Index (从 0 开始,表示 AddressOfFunctions 数组的 index)。如果我们为某个函数使用了 NONAME 指令,那么它在 AddressOfNamesAddressOfNameOrdinals 数组中将没有对应的条目。

    Import module 的导入(现在我们有 Ordinal 而不是 IMAGE_IMPORT_BY_NAME

    Ordinal = 1
    
    Ordinal = 2
    
    Ordinal = 3
    
    Ordinal = 4

按名称导入

当我们按 Name 导入函数时,我们会按以下方式获取其地址:

if (strcmp(FunctionName, AddressOfNames[Hint]))
{
  for (int i = 0; i < NumberOfNames; ++i)
  {
    if (!strcmp(FunctionName, AddressOfNames[i]))
    {
      Hint = i
      break
    }
  }
}
Index = AddressOfNameOrdinals[Hint]
Address = AddressOfFunctions[Index]

按序号导入

当我们按 Ordinal 导入函数时,我们会按以下方式获取其地址:

Address = AddressOfFunctions[Ordinal - Base]

关于其他 def 文件指令的几句话

INTERNALNAME 允许您在 export module 的代码中使用另一个名称(“internal”)。请看这个例子:

来源

void __stdcall internal_name() {}

Def 文件

EXPORTS

external_name = internal_name

从导入/导出机制的角度来看,这等同于:

来源

void __stdcall external_name() {}

Def 文件

EXPORTS

external_name

PRIVATE 允许您构建一个不包含某些符号的 import 库(“partialimport 库)。

使用 dumpbin 获取导出信息

我们可以使用 dumpbin 工具来获取有关 export module 导出信息(我们没有其源代码)的信息。可能的 dumpbin 输出:

这里我们看到修饰后的函数名、提示和序号。

相同,只是函数名未被修饰。

这里只看到序号。由于模块不存储函数名,因此提示也不存在。

请注意,导出始终具有序号,但导出不一定具有名称和提示。

由于调用约定不被存储,我们可以通过反汇编器检查函数代码来确定它。在 x86 上,通常是 __stdcall__fastcall,在 x64 上,通常是 default 调用约定。

Using the Code

对于每个导入的函数,我们需要指定:

  1. 名称
  2. 参数列表大小(字节)
  3. 调用约定
  4. 导入类型

Import 类型:

  1. IMPORT_BY_SPECIFIED_NAME
  2. IMPORT_BY_DECORATED_NAME
  3. IMPORT_BY_ORDINAL

IMPORT_BY_SPECIFIED_NAME:

Import module 将包含未修饰的函数名和 Hint,该 HintValue 参数提供。

Export module 必须存储未修饰的函数名。

IMPORT_BY_DECORATED_NAME:

Import module 将包含修饰后的函数名和 Hint,该 HintValue 参数提供。

Export module 必须存储修饰后的函数名。

IMPORT_BY_ORDINAL:

Import module 将包含 Ordinal,该 OrdinalValue 参数提供。

Export module 不需要存储函数名。

Size of argument list in bytes 参数用于执行函数名修饰。

生成导入库的代码

int main(int argc, char* argv[])
{
    char *pName;
    SYMBOL_INFO *pSymbolList;

    pName = "BOOTVID";

    pSymbolList = CreateSymbolList(pName);

    g_InfoAll.bX64 = FALSE;    // specify TRUE for x64

    AddFunction(pSymbolList, "VidDisplayString", 4, 4, 
                CALLING_CONVENTION_STDCALL, IMPORT_BY_SPECIFIED_NAME);

    WriteImportLibrary(pName, ".dll", pSymbolList);

    DestroySymbolList(pSymbolList);

    return 0;
}

这里,我们指定了架构(x86)、export module 名称(BOOTVID)、export module 扩展名(.dll)和函数列表。

在我们的 import module 的源代码中,我们将以如下方式声明函数:

__declspec(dllimport) void __stdcall VidDisplayString(char *pString);      // C function

后记

我们尚未考虑变量、DATACONSTANT 指令、使用 .def 文件进行导入、__imp_ 的目的以及 C++ 名称。我稍后会更新文章来涵盖所有这些主题。感谢您的阅读。

© . All rights reserved.