Click here to Skip to main content
15,881,882 members
Articles / macOS

Dynamic Linking of Imported Functions in Mach-O

,
Rate me:
Please Sign up or sign in to vote.
4.83/5 (8 votes)
26 Apr 2011CPOL12 min read 41.8K   9  
Knowing the principle of linking of imported functions in Mach-O libraries, we can achieve a rather interesting effect: we can redirect their calls to our code, in which, in its turn, we can use the original one.

Table of Contents

  • Introduction
  • Briefly about Mach-O
  • Fat binary
  • Investigation of the Dynamic Linking
  • Search of an Element in the Import Table
  • Useful Links
  • History

Introduction

Knowing the principle of linking of imported functions in Mach-O libraries, we can achieve a rather interesting effect: we can redirect their calls to our code, in which, in its turn, we can use the original one. To do this, it’s enough to pretend to be a dynamic loader and to correct the import table of the target library in memory. Let’s look at Mach-O format and learn how the dynamic loader performs relocation of its import table.

Briefly about Mach-O

The best way to understand Mach-O is to look at the picture below:

dynamic_linking-in-mach-o/im1.jpg

It seems that the mankind has not managed to describe its structure more clearly yet. At a first approximation, everything looks like the following:

  • Header – here, information on the target architecture and different options of the further file contents interpretation  are stored.
  • Load commands — these commands inform how and where to load Mach-O parts: segments (see below), symbol tables, and also informs which libraries this file depends on to load them first.
  • Segments — these describe regions of memory where to load sections with the code or data.

For the second approximation, we will have to study some utilities-parsers:

  • otool — represents a console program that is provided together with the system. It can display the contents of different parts of the file: headers, load commands, segments, sections, etc. It is especially useful to call it with the -v (verbose) key.
  • MachOView — is distributed under GPL, has its own GUI, and works only under Mac OS 10.6 and higher. It includes the viewer for the full contents of Mach-O, and adds information on some partitions on the basis of data from other partitions and that is very handy.

dynamic_linking-in-mach-o/im2.png

Actually, it is enough for a user to use MachOView on different examples to study Mach-O. But it is not enough for Mach-O development as we don’t know the exact structures of headers, load commands, segments, sections, symbol tables and the exact description of their fields. But it’s not a big problem when having a specification. And it is always available at Apple official site. And if having installed development tools, we can view the header files from /usr/include/mach-o (especially, loader.h).

Besides, it’s worth remembering that though the file contents is located in memory in the same order as it is on the disk, linker still can delete some parts of the symbol table and the whole string table during the load. Also, it can set values of the real offsets in memory where it is necessary while these values in the file can be zeroed or correspond to the offset on the disk.

The header structure is simple (an example is provided for 32-bit architecture; 64-bit architecture does not differ too much):

C++
struct mach_header
{
  uint32_t magic;
  cpu_type_t cputype;
  cpu_subtype_t cpusubtype;
  uint32_t filetype;
  uint32_t ncmds;
  uint32_t sizeofcmds;
  uint32_t flags;
};

Everything begins from a magic value (0xFEEDFACE or vice versa, depending on the agreement concerning the order of bytes in machine words). Then, the processor architecture type, number and size of load commands, and flags that describe other specifics are defined.

For example:

dynamic_linking-in-mach-o/im3.png

The existing load commands are listed below:

  • LC_SEGMENT — contains different information on a certain segment: size, number of sections, offset in the file and in memory (after the load)
  • LC_SYMTAB — loads the table of symbols and strings
  • LC_DYSYMTAB — creates an import table; data on symbols is taken from the symbol table
  • LC_LOAD_DYLIB — defines the dependency from a certain third-party library

For example (for 32- and 64-bit versions, correspondingly):

dynamic_linking-in-mach-o/im4.png dynamic_linking-in-mach-o/im4-2.png

The most important segments are the following:

  • __TEXT — the executed code and other read-only data
  • __DATA — data available for writing; including import tables that can be changed by the dynamic loader during lazy binding
  • __OBJC — different information of the standard library of Objective-C language of execution time
  • __IMPORT — import table only for 32-bit architecture (I managed to generate it only on Mac OS 10.5)
  • __LINKEDIT — here, the dynamic loader places its data for already loaded modules (symbol tables, string tables, etc.)

Any load command starts with the following fields:

struct load_command
{
  uint32_t cmd;  //command numeric code
  uint32_t cmdsize;  //size of the current command in bytes
};

After these fields, there can be many different fields, depending on the type of command.

For example:

dynamic_linking-in-mach-o/im5.png

The most interesting sections in the listed segments are the following:

  • __TEXT,__text — the code itself
  • __TEXT,__cstring — constant strings (in double quotes)
  • __TEXT,__const — different constants
  • __DATA,__data — initialized variables (strings and arrays)
  • __DATA,__la_symbol_ptr — table of pointers to imported functions
  • __DATA,__bss — non-initialized static variables
  • __IMPORT,__jump_table — stubs for calls of imported functions

Beating the gun, I will mention that there can be either __IMPORT,__jump_table (for 32-bit, Mac OS 10.5), or __DATA,__la_symbol_ptr  (for 64-bit, or Mac OS 10.6 and later) as an import table in one Mach-O.

Sections in segments have the following structure:

C++
struct section
{
  char sectname[16];
  char segname[16];
  uint32_t addr;
  uint32_t size;
  uint32_t offset;
  uint32_t align;
  uint32_t reloff;
  uint32_t nreloc;
  uint32_t flags;
  uint32_t reserved1;
  uint32_t reserved2;
};

We have the name of the segment and the section itself, size, offset in the file and the address in memory, by which the dynamic loader located it. Besides, there is other information specific for a certain section.

For example:

dynamic_linking-in-mach-o/im6.png

Fat Binary

Of course, it’s worth mentioning that executable files and libraries “have learned” to store several variants of the executable code at once. It is due to the repeated gradual change of target architectures by the Apple Company (Motorola -> IBM -> Intel). In the general case, such files are called fat binary. In fact, these are several Mach-O gathered in one file but the header of the last is special. It contains information on the number and type of supported architectures and the offsets to each of them. Simple Mach-O with the structure described above are located by such offset.

It looks as follows in the C language:

C++
struct fat_header
{
  uint32_t magic;
  uint32_t nfat_arch;
};

Where magic means 0xCAFEBABE (or vice versa, we should remember about different order of bytes in machine words on different processors). And then, exactly nfat_arch (number) structures of the described below type follow:

C++
struct fat_arch
{
  cpu_type_t cputype;
  cpu_subtype_t cpusubtype;
  uint32_t offset;
  uint32_t size;
  uint32_t align;
};

Actually, the names of fields speak for themselves: type of processor, offset in the file of a certain Mach-O, size, and aligning.

Experimental Program

Let’s take the following files in C language for the investigation of the work of the imported function call:

C++
//File test.c
void libtest();  //from libtest.dylib

int main()
{
    libtest();  //calls puts() from libSystem.B.dylib

    return 0;
}

//File libtest.c
#include <stdio.h>

void libtest()  //just a simple library function
{
    puts("libtest: calls the original puts()");
}

Investigation of the Dynamic Linking

We’ll confine ourselves to Intel processors. Let’s suppose that we have Mac OS 10.5. Let’s add these files to a new Xcode-project, compile (a 32-bit version) and start it in the debug mode. We stop on the line where the call of the puts() function is performed in the libtest() function of the libtest.dylib library. Here is the assembler listing for libtest():

dynamic_linking-in-mach-o/im7.png

Let’s perform one more instruction:

dynamic_linking-in-mach-o/im8.png

And look at it in the memory:

dynamic_linking-in-mach-o/im9.png

This is that cell of the import table (in this case, __IMPORT, __jump_table cell) that serves as a springboard for the call of the dynamic loader (__dyld_stub_binding_helper_interface function) if lazy binding is used, or it jumps right to the target function. This is confirmed by the following call of puts():

dynamic_linking-in-mach-o/im10.png

And in the memory:

dynamic_linking-in-mach-o/im11.png

So, we see that the dynamic loader substituted the instruction of the indirect call of CALL (0xE8) for the instruction of the indirect jump JMP (0xE9). It means that for the redirection of __jump_table elements, it will be enough to write the instruction of the indirect jump to the beginning of the function-substitution instead of their initial contents.

Here is one more interesting moment. Why is it not JMP that is used for the jump to the dynamic loader (linker)? It’s because CALL (which saves the return address in stack) will help the linker to define which element of the import table called it. And so, it will help to define what that symbol was and to solve it by substituting CALL for itself, for the indirect JMP, for the required function.

Now, let’s move the project to Mac OS 10.6 and compile fat binary for 32- and 64-bit architectures. Just in case, you can do it as follows in Xcode:

dynamic_linking-in-mach-o/im12.png

First, we compile, then start the 64-bit variant (just for an example: import table on Snow Leopard will be the same for the 32-bit version too) and stop at the call of puts() again:

dynamic_linking-in-mach-o/im13.png

Here is a simple CALL again. Let’s look next:

dynamic_linking-in-mach-o/im14.png

Here we can see the difference with a simple __IMPORT, __jump_table.

Welcome to __TEXT, __symbol_stub1. This table is a set of JMP instructions for each imported function. In our case, we have only one such instruction that is presented above. Each such instruction performs a jump to the address that is defined in the corresponding cell of the __DATA, __la_symbol_ptr table. The last one is an import table for this Mach-O.

But let’s continue our investigation. If we look at the address, to which the jump is going to be performed:

dynamic_linking-in-mach-o/im15.png

We will see the following:

dynamic_linking-in-mach-o/im16.png

We get into the __TEXT, __stub_helper section. Actually, it’s a PLT (Procedure Linkage Table) for Mach-O. By means of the first instruction (in our case, it’s LEA in the connective with R11 but it could also be a simple PUSH), the dynamic linker remembers, which symbol requires the relocation. The second instruction always leads to one and the same address – to the beginning of the function - __dyld_stub_binding_helper, which will perform linking:

dynamic_linking-in-mach-o/im17.png

After the dynamic linker performs relocations for puts(), the corresponding cell in __DATA, __la_symbol_ptr will look like the following:

dynamic_linking-in-mach-o/im18.png

And this is the address of the puts() function from the libSystem.B.dylib module. It means that we will receive the required effect of the call redirection by replacing the address with our own one.

So, with the help of this specific example we found out how the dynamic linking is performed, what the import tables in Mach-O are and what elements they consist of. Now, let’s move to the analysis of Mach-O.

Search of an Element in the Import Table

We need to find the corresponding cell in the import table by the symbol name. Its algorithm is rather nontrivial.

First, we need to find the symbol itself in the symbol table. The last is the array of the following structures:

C++
struct nlist
{
  union
  {
     int32_t n_strx;
  } n_un;
  uint8_t n_type;
  uint8_t n_sect;
  int16_t n_desc;
  uint32_t n_value;
};

where n_un.n_strx is the offset of the name of this symbol, in bytes from the beginning of the string table. The rest concerns the type of symbol, section where it is placed, etc. In short, here are several last elements for our experimental library libtest.dylib (32-bit version):

 

String table is a chain of names, each of which ends with a zero. But it’s worth mentioning that the compiler adds the underscore character “_” to the beginning of each name. That’s why the name «puts» will look like "_puts" in the string table.

Here is an example:

dynamic_linking-in-mach-o/im20.png

We can find out the location of the symbol table and string table with the help of the corresponding loader command (LC_SYMTAB):

dynamic_linking-in-mach-o/im21.png

But the symbol table is not uniform. There are several partitions in it. We have special interest in one of them: it includes undefined symbols, i.e., those that are linked dynamically. Besides, MachOView highlights these symbols with blue background. To define which part of the symbol table reflects the subset of undefined symbols, we need to look at the loader command of dynamic symbols (LC_DYSYMTAB):

dynamic_linking-in-mach-o/im22.png

Here is its representation in C language:

struct dysymtab_command
{
    uint32_t cmd;
    uint32_t cmdsize;
    uint32_t ilocalsym;
    uint32_t nlocalsym;
    uint32_t iextdefsym;
    uint32_t nextdefsym;
    uint32_t iundefsym;
    uint32_t nundefsym;
    uint32_t tocoff;
    uint32_t ntoc;
    uint32_t modtaboff;
    uint32_t nmodtab;
    uint32_t extrefsymoff;
    uint32_t nextrefsyms;
    uint32_t indirectsymoff;
    uint32_t nindirectsyms;
    uint32_t extreloff;
    uint32_t nextrel;
    uint32_t locreloff;
    uint32_t nlocrel;
};

Here, dysymtab_command.iundefsym is an index in the symbol table, from which the subset of undefined symbols starts. dysymtab_command.nundefsym is a number of undefined symbols. Since we know that we look for an undefined symbol, we should look for it in the symbol table only in this subset. 

And now, one very important moment: when finding the symbol by its name, the most important for us is to remember its index in the symbol table from the beginning. It is because another important table – table of indirect symbols – consists of numerical values of these indexes. We can find it by the value of dysymtab_command.indirectsymoff; dysymtab_command.nindirectsyms defines the number of indexes.

This table consists only of one element in our case (there are much more elements in real life):

dynamic_linking-in-mach-o/im23.png

And finally, let’s look at the section __IMPORT, __jump_table, the element of which we need to find. It looks like the following:

dynamic_linking-in-mach-o/im24.png

The section.reserved1 field for this section is very important (MachOView calls it Indirect Sym Index). It means index in the table of indirect symbols, from which the mutual univocal correspondence with __jump_table elements begins. And we remember that elements in the table of indirect symbols are indexes in the symbol table. Do you catch what I am getting at?

But before collecting everything together, let’s glance over the situation in Snow Leopard to give the complete picture. __DATA, __la_symbol_ptr plays the role of an import table here. Differences are not appreciable too much indeed.

Here is the command of symbols load:

dynamic_linking-in-mach-o/im25.png

And here are its last elements:

dynamic_linking-in-mach-o/im26.png

There are two undefined symbols on the blue background. This corresponds to data from the loader command of dynamic symbols (LC_DYSYMTAB):

dynamic_linking-in-mach-o/im27.png

Also, there are four elements instead of one in the table of indirect symbols:

dynamic_linking-in-mach-o/im28.png

But if we look at the reserved1 field of the required __la_symbol_ptr section, we will discover that the mutual univocal reflection of its elements on the table of indirect symbols starts not from the beginning of the last but from the fourth element (index is equal to 3):

dynamic_linking-in-mach-o/im29.png

The contents of the import table that the __la_symbol_ptr section describes will be as follows:

dynamic_linking-in-mach-o/im30.png

Knowing all these subtleties of Mach-O, we can formulate the algorithm of search of the required element in the import table. That is the matter of issue of the next article.

Useful Links

History

  • 26th April, 2011: Initial post

License

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


Written By
Chief Technology Officer Apriorit Inc.
United States United States
ApriorIT is a software research and development company specializing in cybersecurity and data management technology engineering. We work for a broad range of clients from Fortune 500 technology leaders to small innovative startups building unique solutions.

As Apriorit offers integrated research&development services for the software projects in such areas as endpoint security, network security, data security, embedded Systems, and virtualization, we have strong kernel and driver development skills, huge system programming expertise, and are reals fans of research projects.

Our specialty is reverse engineering, we apply it for security testing and security-related projects.

A separate department of Apriorit works on large-scale business SaaS solutions, handling tasks from business analysis, data architecture design, and web development to performance optimization and DevOps.

Official site: https://www.apriorit.com
Clutch profile: https://clutch.co/profile/apriorit
This is a Organisation

33 members

Written By
Software Developer Meta
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --