The Structure of import Library File (.lib)

This article show the structure of import library file (.lib) used along with header to link against DLL

Introduction

Have you ever wondered about the contents of Microsoft's import library file? This article gives a brief description of .lib file structure and provides source to create import library given the name of export module (.dll; .sys; .exe files) and list of its exported functions (functions can be cherry-picked, so it's not necessary to provide full list).

Two Words About Calling Conventions and Name Decoration

On x86, we can use the following calling conventions for C function:

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

On x64, we can use the following calling conventions for C function (__cdecl, __stdcall, __fastcall are ignored by compiler):

  1. default (function names are not decorated)
  2. __vectorcall

See this link for details about name decoration.

Import Library Structure

Import library is an archive, it starts with arch signature, it is an 8-byte string, namely:

 
 
!<arch>\n

After the signature, we have a bunch of files:

Image 1

Each file starts with fixed-length header, following the arbitrary-length body (header contains length of the body):

Now let's consider symbols "hosted" by import library. Assuming that export module in question is BOOTVID.DLL, we will have three "predefined" symbols:

  1. __IMPORT_DESCRIPTOR_BOOTVID
  2. __NULL_IMPORT_DESCRIPTOR
  3. <7Fh> BOOTVID_NULL_THUNK_DATA // string starts with 0x7F

For each imported function, we will have two additional symbols. Let's consider this C function:

C++
 
void __stdcall VidDisplayString(char *pString);

In x86 version of import library, we will have:

  1. _VidDisplayString@4 // decorated function name
  2. __imp__VidDisplayString@4 // the same string prefixed by __imp_

In x64 version of import library, we will have:

  1. VidDisplayString
  2. __imp_VidDisplayString // the same string prefixed by __imp_

Now let's describe each file contents. First two files in archive contain all symbol names along with offsets to other files. Then we have three files dedicated to three "predefined" symbols. And finally, we have files dedicated to functions (one file for each function).

File 1 contains number of symbols field, symbol names and their corresponding offsets (offsets to another files from the beginning of archive). So File 3 is dedicated to __IMPORT_DESCRIPTOR_BOOTVID, File 4 is dedicated to __NULL_IMPORT_DESCRIPTOR, and so on. Note that only one file is dedicated to each function, despite the fact that function introduces two symbols.

File 2 contains offset table, symbol names and their corresponding indexes (indexes into offset table). It is essentially the same information given in a different form. Note that indexing starts from 1.

Files 3, 4, 5 are dedicated to __IMPORT_DESCRIPTOR_BOOTVID, __NULL_IMPORT_DESCRIPTOR, <7F> BOOTVID_NULL_THUNK_DATA. I won't picture them in detail, they are essentially the same for all import libraries. Instead, I want to focus on File 6 and picture it in detail. Let's see the structure that represents file header:

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

Id field holds random number generated by time function (in ASCII form), BodyLength field holds the length of the following file body (in ASCII form). After file header, we have symbol descriptor:

C++
 
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 field contains 0x14C for x86 and 0x8664 for x64. Id field is the same random number represented in binary form. Hint / Ordinal field contains function hint / ordinal, Type field specifies import type (you will find description further). Length field contains the summary length of two following strings (including their null-characters):

Import / Export Scenarios

We will consider three possible scenarios:

  1. We compile export module and export function without module definition file.

    Input:

    Source:

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

    Output for x86:

    Import library (SYMBOL_DESCRIPTOR):

    C++
     
    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

    Import library stores Hint (starts from 0, represents index into AddressOfNames array).

    Export module's export (AddressOfNames, AddressOfNameOrdinals):

    C++
     
    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

    Despite the name Ordinal, AddressOfNameOrdinals stores Index (starts from 0, represents index into AddressOfFunctions array).

    Import module's import (IMAGE_IMPORT_BY_NAME):

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

    Output for x64:

    Import library (SYMBOL_DESCRIPTOR):

    C++
     
    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

    Import library stores Hint (starts from 0, represents index into AddressOfNames array).

    Export module's export (AddressOfNames, AddressOfNameOrdinals):

    C++
     
    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

    Despite the name Ordinal, AddressOfNameOrdinals stores Index (starts from 0, represents index into AddressOfFunctions array).

    Import module's import (IMAGE_IMPORT_BY_NAME):

    C++
     
    Name = function1, Hint = 0
    
    Name = function2, Hint = 1
    
    Name = function3, Hint = 2
    
    Name = function4@@0, Hint = 3
  2. We compile some DLL and export function using module definition file (we specify function name).

    Input:

    Source:

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

    Def file:

    C++
     
    EXPORTS
    
    function1
    
    function2
    
    function3
    
    function4

    Output for x86:

    Import library (SYMBOL_DESCRIPTOR):

    C++
     
    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

    Import library stores Hint (starts from 0, represents index into AddressOfNames array).

    Export module's export (AddressOfNames, AddressOfNameOrdinals):

    C++
     
    // 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

    Despite the name Ordinal, AddressOfNameOrdinals stores Index (starts from 0, represents index into AddressOfFunctions array).

    Import module's import (IMAGE_IMPORT_BY_NAME):

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

    Output for x64:

    Import library (SYMBOL_DESCRIPTOR):

    C++
     
    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

    Import library stores Hint (starts from 0, represents index into AddressOfNames array).

    Export module's export (AddressOfNames, AddressOfNameOrdinals):

    C++
     
    // 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

    Despite the name Ordinal, AddressOfNameOrdinals stores Index (starts from 0, represents index into AddressOfFunctions array).

    Import module's import (IMAGE_IMPORT_BY_NAME):

    C++
     
    // function names are not decorated
    
    Name = function1, Hint = 0
    
    Name = function2, Hint = 1
    
    Name = function3, Hint = 2
    
    Name = function4, Hint = 3
  3. We compile some DLL and export function using module definition file (we specify function name and ordinal).

    Input:

    Source:

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

    Def file:

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

    Output for x86:

    Import library (SYMBOL_DESCRIPTOR):

    C++
     
    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

    Import library stores Ordinal (starts from 1, represents alternative function name).

    Export module's export (AddressOfNames, AddressOfNameOrdinals):

    C++
     
    // 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

    Despite the name Ordinal, AddressOfNameOrdinals stores Index (starts from 0, represents index into AddressOfFunctions array). If we would use NONAME directive for some function, it would not have corresponding entries in AddressOfNames and AddressOfNameOrdinals arrays.

    Import module's import (now we have Ordinal instead of IMAGE_IMPORT_BY_NAME):

    C++
     
    Ordinal = 1
    
    Ordinal = 2
    
    Ordinal = 3
    
    Ordinal = 4

    Output for x64:

    Import library (SYMBOL_DESCRIPTOR):

    C++
     
    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

    Import library stores Ordinal (starts from 1, represents alternative function name).

    Export module's export (AddressOfNames, AddressOfNameOrdinals):

    C++
     
    // 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

    Despite the name Ordinal, AddressOfNameOrdinals stores Index (starts from 0, represents index into AddressOfFunctions array). If we would use NONAME directive for some function, it would not have corresponding entries in AddressOfNames and AddressOfNameOrdinals arrays.

    Import module's import (now we have Ordinal instead of IMAGE_IMPORT_BY_NAME):

    C++
     
    Ordinal = 1
    
    Ordinal = 2
    
    Ordinal = 3
    
    Ordinal = 4

Import by Name

When we import function by Name, we obtain its address in the following way:

C++
 
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]

Import by Ordinal

When we import function by Ordinal, we obtain its address in the following way:

C++
 
Address = AddressOfFunctions[Ordinal - Base]

Two Words About Another def File Directives

INTERNALNAME allows you to use another name ("internal") in your export module's code. Consider this example:

Source:

C++
 
void __stdcall internal_name() {}

Def file:

C++
 
EXPORTS

external_name = internal_name

From import/export mechanism point of view, it is equivalent to:

Source:

C++
 
void __stdcall external_name() {}

Def file:

C++
 
EXPORTS

external_name

PRIVATE allows you to build import library without some symbol ("partial" import library).

Using dumpbin to Get Export Information

We can use dumpbin tool to get information about export module's exports (for which we do not have source). Possible dumpbin outputs:

Here we see decorated function name, hint and ordinal.

The same, except function name is not decorated.

Here we see ordinal only. Since module does not store function name, hint also does not exist.

Note that export will always have ordinal, however export will not necessarily have name and hint.

Since calling convention is not stored, we can determine it by examining function's code by disassembler. On x86, it is usually __stdcall or __fastcall, on x64, it is usually default calling convention.

Using the Code

For each imported function, we need to specify:

  1. Name
  2. Value
  3. Size of argument list in bytes
  4. Calling convention
  5. Import type

Import types:

  1. IMPORT_BY_SPECIFIED_NAME
  2. IMPORT_BY_DECORATED_NAME
  3. IMPORT_BY_ORDINAL

IMPORT_BY_SPECIFIED_NAME:

Import module will hold undecorated function name and Hint that is given by Value parameter.

Export module must store undecorated function name.

IMPORT_BY_DECORATED_NAME:

Import module will hold decorated function name and Hint that is given by Value parameter.

Export module must store decorated function name.

IMPORT_BY_ORDINAL:

Import module will hold Ordinal that is given by Value parameter.

Export module does not need to store function name.

Size of argument list in bytes parameter is needed to perform function name decoration.

The code to generate import library:

C++
 
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;
}

Here, we specify architecture (x86), export module name (BOOTVID), export module extension (.dll) and function list.

In our import module's source, we would declare the function in the following way:

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

Afterword

We have not considered variables, DATA and CONSTANT directives, importing using .def file, purpose of __imp_, and C++ names. I will update the article later to cover all these topics. Thank you for reading.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章