dyld (dynamic linker/loader) là chương trình chạy đầu tiên khi bạn exec() một binary. Nó load binary, resolve symbols, và chuyển control đến main(). dyld shared cache là optimization chứa hầu hết system frameworks.


Tại Sao Cần Học

  • dyld shared cache chứa hầu hết code system → phân tích targets phải extract từ cache
  • Hook injection (Substrate, Ellekit, libhooker) hoạt động bằng cách hijack dyld mechanisms
  • DYLD_INSERT_LIBRARIES là primitive đơn giản nhất cho code injection (trên macOS)
  • Hiểu symbol resolution giúp hiểu GOT/PLT attacks và lazy binding exploitation

dyld Loading Process

fork() + exec()
  │
  ▼
Kernel:
  1. Parse Mach-O header
  2. Map segments vào virtual memory
  3. Set up stack
  4. Transfer control to dyld (LC_LOAD_DYLINKER)
  │
  ▼
dyld:
  1. Map dyld itself
  2. Load main executable segments
  3. Load dependent dylibs (LC_LOAD_DYLIB)
     └─→ Recursive: mỗi dylib có thể load thêm dylibs khác
  4. Rebase (fix internal pointers for ASLR)
  5. Bind (resolve external symbol references)
     ├─→ Non-lazy: resolve ngay
     └─→ Lazy: resolve khi first call (qua stubs)
  6. Run initializers
     ├─→ +load methods (ObjC)
     ├─→ __attribute__((constructor)) functions
     └─→ C++ static constructors
  7. Call main()

dyld Shared Cache

Khái Niệm

Trên iOS, hầu hết system frameworks (UIKit, Foundation, CoreGraphics, Security, …) được merge vào một file lớn gọi là dyld shared cache:

/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64e
  • Một file ~1-2GB chứa hàng trăm dylibs
  • Mọi processes share cùng mapping → tiết kiệm memory
  • Symbols đã pre-linked → load nhanh hơn
  • ASLR: cache có slide ngẫu nhiên mỗi boot, nhưng mọi processes dùng cùng slide

Tại sao quan trọng cho RE

  • System frameworks không tồn tại dưới dạng file .dylib riêng trên iOS
  • Phải extract individual dylibs từ shared cache để phân tích
  • Gadgets nằm trong shared cache → cùng base address cho mọi processes

Extract dylibs từ Shared Cache

# Dùng dyld_shared_cache_util (Xcode tools)
dyld_shared_cache_util -extract output_dir dyld_shared_cache_arm64e

# Dùng jtool2
jtool2 -e UIKit dyld_shared_cache_arm64e

# Dùng IDA — load shared cache trực tiếp
# File → Open → chọn dyld_shared_cache → chọn dylib cần phân tích

Symbol Resolution

Non-Lazy Binding (resolve tại load time)

__DATA,__got (Global Offset Table)
  ├── slot 0: → address of _objc_msgSend
  ├── slot 1: → address of _NSLog
  └── slot 2: → address of _malloc

dyld fill GOT slots với real addresses khi load binary.

Lazy Binding (resolve khi first call)

Code calls stub:
  __TEXT,__stubs:
    LDR X16, [GOT_slot]    ; load pointer from __la_symbol_ptr
    BR  X16                  ; jump to it

First call → __la_symbol_ptr points to stub_helper:
  __TEXT,__stub_helper:
    LDR W16, lazy_bind_info  ; load bind info offset
    B   dyld_stub_binder     ; call dyld to resolve

dyld resolves → updates __la_symbol_ptr → subsequent calls go directly to real function

Symbol Interposing (Hook mechanism)

// DYLD_INTERPOSE — thay thế function implementation
#define DYLD_INTERPOSE(_new, _orig) \
    __attribute__((used, section("__DATA,__interpose"))) \
    static struct { void *new_func; void *orig_func; } \
    _interpose_##_orig = { (void *)&_new, (void *)&_orig }

int my_open(const char *path, int flags) {
    printf("Opening: %s\n", path);
    return open(path, flags);  // call original
}
DYLD_INTERPOSE(my_open, open);

Tweak Injection Mechanisms

Jailbreak tweak loaders dùng dyld mechanisms để inject code:

CydiaSubstrate / Substitute / Ellekit / libhooker

  1. Inject dylib vào target process (qua DYLD_INSERT_LIBRARIES hoặc custom dyld patch)
  2. Dylib’s __attribute__((constructor)) chạy trước main()
  3. Hook functions bằng cách:
    • Overwrite GOT entries
    • Patch function prologues (inline hook)
    • ObjC method swizzling

Rootless Jailbreak (iOS 15+)

  • Không modify system dyld
  • Inject qua custom environment variables hoặc custom dyld replacement
  • Ellekit: modern hooking framework dùng bởi Dopamine

ASLR (Address Space Layout Randomization)

  • Mỗi process có random slide cho main executable
  • dyld shared cache có random slide per-boot (shared giữa processes)
  • Kernel có KASLR — random slide mỗi boot
Base address (trong Mach-O):  0x100000000
ASLR slide:                   0x000012340000 (random)
Runtime address:              0x100012340000

Tài Nguyên


Bài Tập

  1. Trace dyld loading: Trên macOS, set DYLD_PRINT_LIBRARIES=1 và run binary, observe load order
  2. Extract dylib từ shared cache: Extract UIKit hoặc Foundation, mở trong IDA/Ghidra
  3. Write interpose dylib: Hook open() syscall, log mọi file access, inject vào app trên macOS
  4. Analyze lazy binding: Đặt breakpoint tại stub, trace execution flow qua stub_helper → dyld → real function
  5. Calculate ASLR slide: Từ vmmap output, tính slide cho main executable và shared cache