Understanding Static and Dynamic Linking in iOS Apps and Their Use for Static Library Hooking
This article explains the principles of static and dynamic linking on iOS, details how symbol tables, relocation entries, and GOT/PLT work, and demonstrates a practical static‑library hooking technique using relocation tables and the fishhook library.
In iOS app development, Xcode relies on LLVM to perform linking, which lets developers concentrate on business logic, but a solid grasp of linking fundamentals provides deeper insight into the iOS runtime and helps solve low‑level issues.
The article first introduces static linking: source files (.c) are compiled into object files, each producing a symbol table; the linker merges these tables, resolves addresses, and creates a single global symbol table for the executable.
Space and address allocation are illustrated with a diagram of similar‑section merging, followed by an explanation of relocation tables that record where symbol addresses must be patched after layout is fixed.
// relocation_info struct
struct relocation_info {
int32_t r_address; /* offset in the section to what is being relocated */
uint32_t r_symbolnum:24, /* symbol index if r_extern == 1 or section ordinal if r_extern == 0 */
r_pcrel:1, /* was relocated pc relative already */
r_length:2, /* 0=byte, 1=word, 2=long, 3=quad */
r_extern:1, /* does not include value of sym referenced */
r_type:4; /* machine specific relocation type */
};An example shows a hook for objc_msgSend implemented by modifying the string table entry and letting the relocation process rewrite the call site to the new hook function.
Static‑library hooking steps are summarized:
Replace the objc_msgSend string in the .o files of the static library with hook_msgSend (using a script).
Implement hook_msgSend in the main project.
During linking, the relocation table redirects calls to the new implementation.
The article then shifts to dynamic linking, explaining why Position‑Independent Code (PIC) is needed for ASLR and how dynamic symbol binding works via the Global Offset Table (GOT) and Procedure Linkage Table (PLT).
Key Mach‑O load commands are listed to show where the GOT, indirect symbol table, and symbol table reside:
// mach_header_64
struct mach_header_64 {
uint32_t magic;
cpu_type_t cputype;
cpu_subtype_t cpusubtype;
uint32_t filetype;
uint32_t ncmds;
uint32_t sizeofcmds;
uint32_t flags;
uint32_t reserved;
};
// dysymtab_command (indirect symbol table)
struct dysymtab_command {
...
uint32_t indirectsymoff; /* file offset to the indirect symbol table */
uint32_t nindirectsyms; /* number of indirect symbol table entries */
};
// symtab_command (symbol table)
struct symtab_command {
uint32_t cmd; /* LC_SYMTAB */
uint32_t cmdsize;
uint32_t symoff; /* symbol table offset */
uint32_t nsyms; /* number of symbols */
uint32_t stroff; /* string table offset */
uint32_t strsize;/* string table size */
};
// segment_command_64 (segment description)
struct segment_command_64 {
uint32_t cmd; /* LC_SEGMENT_64 */
uint32_t cmdsize;
char segname[16];
uint64_t vmaddr;
uint64_t vmsize;
uint64_t fileoff;
uint64_t filesize;
vm_prot_t maxprot;
vm_prot_t initprot;
uint32_t nsects;
uint32_t flags;
};
// section_64 (section description)
struct section_64 {
char sectname[16];
char segname[16];
uint64_t addr;
uint64_t size;
uint32_t offset;
uint32_t align;
uint32_t reloff;
uint32_t nreloc;
uint32_t flags;
uint32_t reserved1;
uint32_t reserved2;
uint32_t reserved3;
};Dynamic symbol binding proceeds by creating pointers in the data segment (GOT) that are later replaced with the real addresses of functions from shared libraries after the dyld loader resolves them.
The article then introduces the open‑source fishhook library, which hijacks symbols by rewriting GOT entries. A minimal usage example is provided:
void myLog(NSString *format, ...) {
printf("mylog");
}
static void (*originLog)(NSString *format, ...);
+ (void)load {
struct rebinding r1 = {"NSLog", myLog, (void *)&originLog};
struct rebinding rebind[1] = {r1};
rebind_symbols(rebind, 1);
}Fishhook works by walking the load commands to locate the symbol table, indirect symbol table, and GOT, then replacing the GOT pointers with the addresses of the user‑provided functions.
Finally, the article summarizes that while it focused on linking and hooking, many related topics—such as symbol stripping, manual library loading, and deeper symbol resolution—remain open for further exploration, and that understanding these low‑level mechanisms can greatly improve debugging, optimization, and overall code quality.
JD Retail Technology
Official platform of JD Retail Technology, delivering insightful R&D news and a deep look into the lives and work of technologists.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.