Files
knowledge-kit/Chapter1 - iOS/1.91.md
2022-05-24 13:00:23 +08:00

8.0 KiB
Raw Blame History

DYLD

dynamic loader动态加载器。在 MacOS/iOS 中,使用 /usr/lib/dyld 程序来加载动态库的。

DYLDdyld shared cache 动态库共享缓存

UIKit、CoreGraphics 等。从 iOS13 开始为了提高性能绝大部分的系统动态库文件都被打包存放到了一个缓存文件中dyld shared cache中。 存放路径为:/System/Libarey/Caches/com.apple.dyld/dyld_shared_cache_armX 其中,X 代表 ARM 处理器的指令集架构。V6、V7、V7s、arm64、arm64e。不同架构对应的动态库缓存不一致。

所有指令集原则是向下兼容的。动态库共享缓存一个非常明显的好处是节省内存。

具体底层源码可以查看dyld 源码中 dyld2.cpp 文件,函数入口为 load 方法。

ImageLoader* load(const char* path, const LoadContext& context, unsigned& cacheIndex);

某个 App 被第一次打开时候, dyld 根据动态库的依赖,循环加载动态库到动态库共享缓存中(内存),后续其他 App 第一次打开发现使用了动态库A已经在动态库共享缓存中存在则不需要加载。这一步调用方法为 findInSharedCacheImage

dyld 应用

窥探系统库底层实现的时候可能需要从动态库共享缓存中提取出某个 Framework比如 UIKit。这时候要么用第三方工具要么用 dyld 的能力。

查看 dsc_extractor.cpp 代码可以看到是具备抽取能力的。修改源码,将 if 宏定义去掉。如下

#include <stdio.h>
#include <stddef.h>
#include <dlfcn.h>


typedef int (*extractor_proc)(const char* shared_cache_file_path, const char* extraction_root_path,
                              void (^progress)(unsigned current, unsigned total));

int main(int argc, const char* argv[])
{
    if ( argc != 3 ) {
        fprintf(stderr, "usage: dsc_extractor <path-to-cache-file> <path-to-device-dir>\n");
        return 1;
    }

    //void* handle = dlopen("/Volumes/my/src/dyld/build/Debug/dsc_extractor.bundle", RTLD_LAZY);
    void* handle = dlopen("/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/usr/lib/dsc_extractor.bundle", RTLD_LAZY);
    if ( handle == NULL ) {
        fprintf(stderr, "dsc_extractor.bundle could not be loaded\n");
        return 1;
    }

    extractor_proc proc = (extractor_proc)dlsym(handle, "dyld_shared_cache_extract_dylibs_progress");
    if ( proc == NULL ) {
        fprintf(stderr, "dsc_extractor.bundle did not have dyld_shared_cache_extract_dylibs_progress symbol\n");
        return 1;
    }

    int result = (*proc)(argv[1], argv[2], ^(unsigned c, unsigned total) { printf("%d/%d\n", c, total); } );
    fprintf(stderr, "dyld_shared_cache_extract_dylibs_progress() => %d\n", result);
    return 0;
}

然后用 clang++ 编译,命令为 clang++ dsc_extractor.cpp

将编译后的产物复制到动态库共享缓存目录下去。然后执行命令./dsc_extractor dyld_shared_cache_armv7s armv7s,代表将动态库提取到 armv7s 目录下。

Mach-O

Mach Object 的缩写,是 iOS/MacOS 上用于存储程序、库的标准格式

在 XNU 源码中可以查看 Mach-O 的定义。loader.h

属于 Mach-O 格式的文件类型有:

#define    MH_OBJECT    0x1        /* relocatable object file */
#define    MH_EXECUTE    0x2        /* demand paged executable file */
#define    MH_FVMLIB    0x3        /* fixed VM shared library file */
#define    MH_CORE        0x4        /* core file */
#define    MH_PRELOAD    0x5        /* preloaded executable file */
#define    MH_DYLIB    0x6        /* dynamically bound shared library */
#define    MH_DYLINKER    0x7        /* dynamic link editor */
#define    MH_BUNDLE    0x8        /* dynamically bound bundle file */
#define    MH_DYLIB_STUB    0x9        /* shared library stub for static */
                    /*  linking only, no section contents */
#define    MH_DSYM        0xa        /* companion file with only debug */
                    /*  sections */
#define    MH_KEXT_BUNDLE    0xb        /* x86_64 kexts */

常见的 Mach-O 文件类型

MH_OBJECT

  • 目标文件(.o

  • 静态库文件(.a静态库其实就是 N 个 .o 合并在一起

MH_EXECUTE可执行文件

MH_DYLIB动态库文件

  • dylib

  • .framework

MY_DYLINKER动态链接编辑器 (/usr/lib/dyld)

MH_DSYM存储着二进制文件符号。.dSYM/Contents/Resource/DWARF/xx 常用于还原堆栈

Xcode 中也可以查看 Mach-O 文件类型

Universal Binary

通用二进制文件,可以同时适用于多种架构的二进制文件,包含多种不同架构的独立的二进制文件。

因为要存储多种架构的代码,所以通用二进制文件通常比单一平台的二进制程序更大

由于两种架构有共同的一些资源所以并不会达到单一版本的2倍多。

执行的过程中,只调用一部分代码,运行起来不需要额外的内存。

因为通用二进制文件比原来的大所以被成为“胖二进制文件”Fat Binary

查看某可执行文件(Test)支持的架构指令集

lipo -info Test

将某个指令集拆出来比如 arm64

lipo Test -thin arm64 -o Test_arm64

也可以将多个指令集合并

lipo -create Test_arm64 Test_armv7 -output Test_universal

Mach-O 结构

一个 Mach-O 文件包含3块

  • Header文件类型、目标架构类型信息

  • Load commands描述文件在虚拟内存中的逻辑结构、布局

  • Raw segment data在 Load Commands 中定义的 segment 的原始数据

可以用 GitHub - fangshufeng/MachOView: 分析Macho必备工具 和系统自带的 atool 查看 Mach-O 信息

用 MachOView 查看 DDD Mach-O 文件

可以看到在 Mach-O 文件上,__PAGEZEROVM Size 有值,但是 File Size 为0也就是说 __PAGEZERO 在 Mach-O 中不占据内存,但是程序运行起来之后,会占据虚拟内存。所以代码段在 Mach-O 中 File Offset 为0如果前面的 __PAGEZERO 的 File size 有值,这里的 File Offset 就不为0

在没有 ASLR 的时候__TEXT 代码段地址 = __PAGEZEROR 地址

ASLR

未使用 ASLR 的问题

  • 函数代码存放在 __TEXT

  • 全局变量存放在 __DATA

  • 可执行文件的内存地址为 0x0

  • 可执行文件 Header 的内存地址,就是 LC_SEGMENT(__TEXT) 中的 VM Address

    • arm64 0x100000000

    • 非 arm640x4000

也可以使用 size -l -m -x DDD 指令来查看 Mach-O 的内存分布

我们会发现根据 Mach-O 文件中的信息File Size、File Offset、VM Address、VM Size 可以判断出 __TEXT 段内函数信息,这样子不够安全

ASLR 诞生

Address Space Layout Randomization地址空间布局随机化。是一种针对缓冲区溢出的安全保护技术通过堆、栈、共享库映射等线性区布局的随机化通过增加攻击者预测目的地址的难度防止攻击者直接定位攻击代码的位置达到阻止溢出攻击目的的一种技术。在 iOS 4.3 引入。

  • LC_SEGMENT (__TEXT) 的 VM Address 0x10005000

  • ASLR 随机偏移 0x5000也就是可执行文件的内存地址

在有 ASLR 的时候__TEXT 代码段地址 = __PAGEZEROR 地址

在 Mach-O 文件中的地址是原始地址

代码运行起来函数真实地址 = ASLR-Offset + __PAGEZEROarm640x100000000其他0x4000+ 函数基于 Mach-O 的地址