Sunteți pe pagina 1din 6

! ! !

MACH-O DYNAMIC LINKING ANALYSIS & HOOKING


JONATHAN DANIEL* FEBRUARY 18, 2014

Abstract
Many a time it might be useful to alter the behavior of an external process without disturbing the execution of it. For example, one might want to add functionality to a program, or retrieve and modify parameters passed to library calls. This paper will explain the process of calling library functions in the Mach-O file format and the redirection of dynamic library function calls on this specific format. Understanding the principals of the dynamic linker is essential for the creation of format specific hooks. Since hooking is widely used under Windows (in various ways) and the user base of Mac-OSX is growing, I decided to analyze the file format.

"It doesnt get PC viruses" Apple Inc.


Content Introduction Chapter 1 Mach-O Dynamic Linking 1.1 The Mach-O Layout 1.2 Sample program 1.3 Disassembly Chapter 2 Mach-O Detouring Conclusion References

*Contact: Jonathan Daniel jona.t4@technologist.com, GuidedHacking

Introduction
Changing the import table of a library in memory can be used for various purposes. To be able to patch the import table we must know the principal of dynamic linking of the concerning file format. Knowing that we can counterfeit a dynamic linker and find a specific function pointer to detour with our hook. Since Mac-OSXs user base is growing and hooking is widely used under Microsoft Windows, it is time to start detouring functions on Mac-OSX. This paper is organized as follows. Chapter 1 explains the structure of the format and how linking is done. Chapter 2 explains how hooking is performed.

Segments Segments are made of sections, all segments are relative to each other, which is useful for offsets. Sections contain code or data. Link edit The link edit section is important for us. As it contains information used by the dynamic linker such as the symbol & string table. More on these laterI.
Figure 1 (Copyright Apple Inc.)

Mach-O Dynamic Linking

The Mach-O (Mach object) file format, is the standard used to store programs and libraries on disk in the Mac app binary interface (ABI). To understand how the dynamic linker works with Mach-O files, and to perform detouring (hooking) tasks, you need to understand this information.

1.1

The Mach-O Layout

1.2

Sample program

The Mach-O format contains four major regions (as shown in Figure 1): Header Structure - Mach-O signature and other info (like CPU type). Load commands Contains info about the layout of the file and the location of the symbol table (used for dynamic linking). Which is of course very important for us. It also tells us the names of the shared libraries used by the executable.

For the rest of the paper (about Mach-O) I will be analyzing this sample program as example.
#include <stdio.h> // A prototype for a function from a // dynamic library (libtest.dylib) void libtest(); int main (int argc, const char * argv[]) { libtest(); libtest(); getchar(); return 0; }

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! "!OS X ABI Mach-O File Format Reference by Apple Inc. !

1.3

Disassembly
Our libtest stub is located at address 0x100000f20. Every function stub consists of just one instruction, a jmp: (gdb) disassemble 0x100000f14 0x100000f26 Dump of assembler code from 0x100000f14 to 0x100000f20: 0x0000000100000f14 <dyld_stub_exit+0>: jmpq *0x12e(%rip) 0x0000000100000f1a <dyld_stub_getchar+0>: jmpq *0x130(%rip) 0x0000000100000f20 <dyld_stub_libtest+0>: jmpq *0x132(%rip)

To disassemble & read the contents of our sample program we will mainly be using two tools: Gdb For dumping assembler code Otool Open source tool to read out Machobject files. Xcode Reading memory and compiling, Before starting with disassembling you will have to know the most important segments and sections: __TEXT __text Executable machine code only. __TEXT __symbol_stub Indirect symbol stubs. __DATA __la_symbol_ptr Lazy symbol pointers, which are indirect references to imported functions. Lazy symbols is what we are looking for. Lets take a look at how the functions get called in the TEXT __text section and how the linker resolves them:
0x100000ed9 mov 0x100000ede callq 0x100000ee3 mov 0x100000ee8 callq 0x100000eed callq $0x0,%eax 0x100000f20 <dyld_stub_libtest> $0x0,%eax 0x100000f20 <dyld_stub_libtest> 0x100000f1a <dyld_stub_getchar>

# 0x100001048 # 0x100001050 # 0x100001058

Welcome to __DATA __la_symbol_ptr, as you can see from the output of otool below, it jumps to the function at index 20: Indirect symbols for (__DATA,__la_symbol_ptr) 4 entries address index 0x0000000100001040 16 0x0000000100001048 18 0x0000000100001050 19 0x0000000100001058 20 If you look at the memory at address 0x100001048 you will find the address the jump instruction jumps to 0x0000000100001048 40 0f 00 00 01 00 00 00 0x0000000100001050 4a 0f 00 00 01 00 00 00 # Note: big/little endian That is 0x0000000100000f40 and the next one is 0x0000000100000f4a and so forth. These addresses point to stub helpers in the __TEXT __stub_helper section, each function has its own stub helper, each stub helper basically pushes (or loads) the function index and then calls the linker. That way the linker resolves the pointer for the specific function and patches the symbol pointer table ( __DATA, __la_symbol_ptr ) to point to the correct function address. The stub helper section is the so-called PLT (Procedure Linkage Table) for the Mach-O formatII.

As expected, two calls to libtest and one to getchar, Ive removed the rest of the dump. As you can see gdb helped us and named the addresses dyld_stub_x, this is because every imported function has his own function stub in the __TEXT __symbolstub1 section. This section looks as follows for our program (output from otool): Indirect symbols for (__TEXT,__symbol_stub1) 4 entries address index 0x0000000100000f0e 16 0x0000000100000f14 18 # dyld_stub_exit 0x0000000100000f1a 19 # dyld_stub_getchar 0x0000000100000f20 20 # dyld_stub_libtest

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
II

Dynamic Linking of Imported Functions in Mach-O by Apriorit Inc, Anthony Shoumihin.

In our example application the PLT looks like this (each individual stub is bolded). (gdb) disassemble 0x0000000100000f40 0x0000000100000f4a Dump of assembler code from 0x100000f40 to 0x100000f59: 0x0000000100000f40 < stub helpers+26>: pushq $0x18 0x0000000100000f45 < stub helpers+31>: jmpq 0x100000f26 < stub helpers> 0x0000000100000f4a < stub helpers+36>: pushq $0x24 0x0000000100000f4f < stub helpers+41>: jmpq 0x100000f26 < stub helpers> 0x0000000100000f54 < stub helpers+46>: pushq $0x33 0x0000000100000f59 < stub helpers+51>: jmpq 0x100000f26 < stub helpers> End of assembler dump. As seen in the assembler code above, each stub pushes a function ID and calls the linker (for every stub the same address) at 0x100000f26. The stub at address 0x0000000100000f40 pushes 0x18 as ID, this can be seen again at the __DATA __la_symbol_ptr section. Figure 2 summarizes the important mentioned sections and figure 3 is a graphical view of the calling process.
Figure 3 : Graphical overview of calling process

Figure 2 : Relevant Sections SummaryIII

Indirect symbols for (__TEXT,__symbol_stub1) 4 entries address index 0x0000000100000f0e 16 0x0000000100000f14 18 # dyld_stub_exit 0x0000000100000f1a 19 # dyld_stub_getchar 0x0000000100000f20 20 # dyld_stub_libtest Indirect symbols for (__DATA,__la_symbol_ptr) 4 entries address index 0x0000000100001040 16 0x0000000100001048 18 0x0000000100001050 19 0x0000000100001058 20 Contents of (__TEXT,__stub_helper) section stub helpers: 0000000100000f36 pushq $0x00000000 0000000100000f3b jmp 0x200000f26 0000000100000f40 pushq $0x00000018 0000000100000f45 jmp 0x200000f26 0000000100000f4a pushq $0x00000024 0000000100000f4f jmp 0x200000f26

##$%&$!##'()'!

##$%&$! ##*12,3.#*'+,!

##$%&$! ##*'+,#-(./(0!

##45$5! ##.6#*12,3.#/'0!

78,0601!9+:;'83:!

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! """!Output from Otool!

Mach-O Detouring

Knowing the process of calling imported (lazy) functions gives us the opportunity to examine a way to find a specific element from the import table through its corresponding function name. The beginning of a Mach-object file contains the mach_header structure as described in loader.h:
/* * The 32-bit mach header appears at the very beginning of the object file for * 32-bit architectures. */ struct mach_header { uint32_t magic; /* mach magic number identifier */ cpu_type_t cputype; /* cpu specifier */ cpu_subtype_t cpusubtype; /* machine specifier */ uint32_t filetype; /* type of file */ uint32_t ncmds; /* number of load commands */ uint32_t sizeofcmds; /* the size of all the load commands */ uint32_t flags; /* flags */ };

!
Note that some binaries are actually fat-binaries (which are binaries that contain code for more than one architecture) which means they start with the following structure (declared in fat.h):
struct fat_header { uint32_t magic; uint32_t nfat_arch; }; /* FAT_MAGIC */ /* number of structs that follow */

By reading the first 32 bits of a file you can find out if it the magic number is FAT_MAGIC or not. A fat header is basically an array of mach_headers (nfat_arch is the amount of headers) for different architectures. After the headers come the load commands.

Loader.h: /* * The load commands directly follow the mach_header. The total size of all * of the commands is given by the sizeofcmds field in the mach_header. All * load commands must have as their first two fields cmd and cmdsize. The cmd * field is filled in with a constant for that command type. Each command type * has a structure specifically for it. The cmdsize field is the size in bytes * of the particular load command structure plus anything that follows it that * is a part of the load command (i.e. section structures, strings, etc.). To * advance to the next load command the cmdsize can be added to the offset or * pointer of the current load command. The cmdsize for 32-bit architectures * MUST be a multiple of 4 bytes and for 64-bit architectures MUST be a multiple * of 8 bytes (these are forever the maximum alignment of any load commands). * The padded bytes must be zero. All tables in the object file must also * follow these rules so the file can be memory mapped. Otherwise the pointers * to these tables will not work well or at all on some machines. With all * padding zeroed like objects will compare byte for byte. */ struct load_command { uint32_t cmd; /* type of load command */ uint32_t cmdsize; /* total size of command in bytes */ };!

The load commands are interesting for us because the LC_SYMTAB command contains information about the symbol and string tables. Since we need to find an element in the import table by the corresponding symbol table entry.

/* * The symtab_command contains the offsets and sizes of the link-edit 4.3BSD * "stab" style symbol table information as described in the header files * <nlist.h> and <stab.h>. */ struct symtab_command { uint32_t cmd; /* LC_SYMTAB */ uint32_t cmdsize; /* sizeof(struct symtab_command) */ uint32_t symoff; /* symbol table offset */ uint32_t nsyms; /* number of symbol table entries */ uint32_t stroff; /* string table offset */ uint32_t strsize; /* string table size in bytes */ };

The symbol table is an array of structures (nlists) and each structure represents an element. As you can see there is no element that represents the name of the symbol. Therefore we need to look at the string table, with n_strx as offset from the beginning of the string table for this specific element. The problem is that the symbol table contains various types of elements that we do not need, we are only interested in the undefined symbols or also called dynamically linked symbols.

struct nlist { union { uint32_t }; uint8_t uint8_t uint16_t uint32_t };

n_strx;

n_type; n_sect; n_desc; n_value;

That is why we also need to read the dysymtab_command, because it contains information about the indirect symbol table (will explain about this later) & the location of the undefined symbols in the symbol table (note: NOT the indirect symbol table).
struct dysymtab_command { uint32_t cmd; uint32_t cmdsize; uint32_t ilocalsym; uint32_t nlocalsym; uint32_t iextdefsym; uint32_t nextdefsym; uint32_t iundefsym; /* Index from the start of the symbol table from where the dynamic symbols start */ uint32_t nundefsym; /* Number of dynamic symbols in the symbol table */ uint32_t tocoff; uint32_t ntoc; uint32_t modtaboff; uint32_t nmodtab; uint32_t extrefsymoff; uint32_t nextrefsyms; uint32_t indirectsymoff; /* Offset to the indirect symbol table */ uint32_t nindirectsyms; /* Number of elements in the indirect symbol table */ uint32_t extreloff; uint32_t nextrel; uint32_t locreloff; uint32_t nlocrel; };

!When we have found the symbol in the symbol table, we still dont know its index in the jump table
(__la_symbol_ptr or __jump_table). That is what we need the indirect symbol table for, every element in the jump table has a field that specifies its corresponding index in the indirect symbol table, and in the indirect symbol table you can find the index to the the symbol table, and in the symbol table you can then finally find the index to the string table. The following are the elements of the indirect symbol table in our example:
Indirect symbols for (__DATA,__la_symbol_ptr) 4 entries address index 0x0000000100001040 16 ___stack_chk_fail 0x0000000100001048 18 _exit 0x0000000100001050 19 _getchar 0x0000000100001058 20 _libtest

! !

S-ar putea să vă placă și