扣丁书屋

iOS 恢复调用栈(适配iOS14)

0x00 前言

之前杨君大佬写过这个工具:iOS符号表恢复 65,恢复后堆栈可显示函数信息。

不知道为何我使用后,堆栈依旧没有显示出OC函数来。所以整理下资料,在restore-symbol 18工程上再适配 iOS14。

不同场景下,也可以用xia0大佬写过的 Frida调用栈符号恢复 28,利用 runtime 机制动态获取函数信息,近似匹配来恢复调用栈。

0x01 符号与调用栈

1. 调用栈

在分析程序 crash 时,一般会查看调用栈来定位问题。如果可执行文件没有经过 strip,符号表还保存着符号信息,调用栈是可以显示函数名称的。

  • 无符号调用栈
    "0   AlipayWallet                        0x0000000107545f18 _ZN7gcanvas14GCommandBuffer10parseArrayIfEEbPT_m + 29852464",
      "1   AlipayWallet                        0x0000000107567f94 _ZN7gcanvas14GCommandBuffer10parseArrayIfEEbPT_m + 29991852",
      "2   AlipayWallet                        0x0000000107571b98 _ZN7gcanvas14GCommandBuffer10parseArrayIfEEbPT_m + 30031792",
      "3   AlipayWallet                        0x00000001075a82d8 _ZN7gcanvas14GCommandBuffer10parseArrayIfEEbPT_m + 30254832",
      "4   AlipayWallet                        0x000000010758f778 _ZN7gcanvas14GCommandBuffer10parseArrayIfEEbPT_m + 30153616",
      "5   AlipayWallet                        0x00000001075745b4 _ZN7gcanvas14GCommandBuffer10parseArrayIfEEbPT_m + 30042572",
      "6   AlipayWallet                        0x0000000107557770 _ZN7gcanvas14GCommandBuffer10parseArrayIfEEbPT_m + 29924232",
      "7   libsystem_pthread.dylib             0x0000000183459d8c _pthread_start + 156",
      "8   libsystem_pthread.dylib             0x000000018345d76c thread_start + 8"

没有符号的调用栈无法给出有效函数信息,导致无法定位问题所在。

2. 符号

  • 符号(Symbol:类名、函数名或变量名称为符号名 (Symbol Name);

  • 按类型分,符号可以分三类:

  • 全局符号:目标文件外可见的符号,可以被其他目标文件引用,或者需要其他目标文件定义;

  • 局部符号:只在目标文件内可见的符号,指只在目标文件内可见的函数和变量;

  • 调试符号:包括 行号 信息的调试符号信息,行号信息中记录了函数和变量所对应的 文件和文件行号。

  • 符号表(Symbol Table:符号表是 内存地址与函数名、文件名、行号 的映射表;每个定义的符号有一个对应的值,叫做 符号值(Symbol Value),对于变量和函数来说,符号值就是他们的地址;符号表元素如下所示:

< 起始地址 > < 结束地址 > < 函数 > [< 文件名: 行号 >]
  • dSYM(debug symbols):是 iOS 的 符号表文件,存储 16 进制 地址信息和符号的映射文件;文件名通常为:xxx.app.dSYM,类似 Android 构建 release 产生的 mapping 文件;利用 dSYM 文件文件,可以将堆栈信息中地址信息还原成对应的符号,帮助问题排查;

App relaese 包一般会 strip 掉局部符号和调试符号以减小包体积,去掉这些符号不影响 App 正常运行,也可以一定程度上保护 App。

App 被 strip 掉符号后,调用栈无法显示函数名称。如果得到函数名称与地址,恢复符号表,将符号表 patch 到可执行文件后,理论上调用栈就可以显示出信息。

0x02 获取函数信息

1. objective-c 函数

一般情况下 App strip 掉符号后,如果不借助外部 dSYM 文件,是无法恢复的函数信息,比如 C 函数在可执行文件仅剩地址。

objective-c 函数信息除了保存在符号表中,还保存在其他段中

__TEXT.__objc_methname - Method names for locally implemented methods
__TEXT.__objc_classname - Names for locally implemented classes
__TEXT.__objc_methtype - Types for locally implemented method types
__DATA.__objc_classlist - An array of pointers to ObjC classes
__DATA.__objc_nlclslist - An array of pointers to classes who implement +load
__DATA.__objc_catlist - List of ObjC categories
__DATA.__objc_protolist - List of ObjC protocols
__DATA.__objc_imageinfo - Version info, not really useful
__DATA.__objc_const - Constant data, i.e. class_ro_t data
__DATA.__objc_selrefs - External references to selectors
__DATA.__objc_protorefs - External references to protocols
__DATA.__objc_classrefs - External references to other classes
__DATA.__objc_superrefs - External references to super classes
__DATA.__objc_ivar - Offsets to ObjC properties
__DATA.__objc_data - Misc ObjC storage, notably ObjC classes

strip 仅删除符号表中的数据,所以 OC 符号可以恢复。

2. 解析 __objc_* section

从 objc 的开源代码来看如何解析 __objc_* section

i. 现用 class 结构

typedef struct objc_class *Class;

struct objc_object {
private:
    isa_t isa; // 一个指针大小
    ...
}
struct objc_class : objc_object {
    // Class ISA;
    Class superclass; // 一个指针大小
    cache_t cache;     // 两个指针大小        // formerly cache pointer and vtable
    class_data_bits_t bits; // 一个指针大小   // class_rw_t * plus custom rr/alloc flags
    ...
}

class_data_bits_t 是一个指针大小的结构体,保存着类的方法、属性、遵循的协议等信息。

struct class_data_bits_t {
    // Values are the FAST_ flags above.
    uintptr_t bits;
    ...

  class_rw_t* data() {return (class_rw_t *)(bits & FAST_DATA_MASK);
  }

  const class_ro_t *safe_ro() const {class_rw_t *maybe_rw = data();
    if (maybe_rw->flags & RW_REALIZED) {
      // maybe_rw is rw
      return maybe_rw->ro();} else {
      // maybe_rw is actually ro
      return (class_ro_t *)maybe_rw;
    }
  }

}

在编译期间,class_ro_t 结构体就已经确定,objc_class 中的 bits 的 data 部分存放着该结构体的地址。

运行 runtime 的 realizeClass 方法时,会生成 class_rw_t 结构体,该结构体包含了 class_ro_t,并且更新 data 部分,换成 class_rw_t 结构体的地址。

所以保存在 Mach-O 可执行文件中的结构是 class_ro_t。

在 iOS14 后,class_rw_t 结构体发生较大变化,class_ro_t 结构体未变化。

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;

    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
};

ii、方法、属性、协议

class_ro_t 里面的 method_list_t 、 ivar_list_t 和 property_list_t 都使用 entsize_list_tt 的结构体模板;iOS 14 method_list_t 有较大改变

// iOS 14 以下
template <typename Element, typename List, uint32_t FlagMask>
struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;
    Element first;
      // other code
    uint32_t entsize() const {return entsizeAndFlags & ~FlagMask;}
}

struct method_list_t : entsize_list_tt<method_t, method_list_t, 0x3> {...};

struct ivar_list_t : entsize_list_tt<ivar_t, ivar_list_t, 0> {...};

struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};


// iOS14 以下
struct method_t {
    SEL name;
    const char *types;
    MethodListIMP imp;
  ...
};
  • 方法
// iOS 14
template <typename Element, typename List, uint32_t FlagMask, typename PointerModifier = PointerModifierNop>
struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;
    uint32_t entsize() const {return entsizeAndFlags & ~FlagMask;}
    uint32_t flags() const {return entsizeAndFlags & FlagMask;}
};

struct method_list_t : entsize_list_tt<method_t, method_list_t, 0xffff0003, method_t::pointer_modifier> {uint32_t indexOfMethod(const method_t *meth) const {
        uint32_t i = 
            (uint32_t)(((uintptr_t)meth - (uintptr_t)this)/ entsize());
        ASSERT(i < count);
        return i;
    }

    bool isSmallList() const {return flags() & method_t::smallMethodListFlag;
    }
};


struct method_t {
    static const uint32_t smallMethodListFlag = 0x80000000;

    method_t(const method_t &other) = delete;

    // The representation of a "big" method. This is the traditional
    // representation of three pointers storing the selector, types
    // and implementation.
    struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };

private:
    bool isSmall() const {return ((uintptr_t)this & 1)== 1;}

    // The representation of a "small" method. This stores three
    // relative offsets to the name, types, and implementation.
    struct small {
        // The name field either refers to a selector (in the shared
        // cache)or a selref (everywhere else).
        RelativePointer<const void *> name;
        RelativePointer<const char *> types;
        RelativePointer<IMP> imp;

        bool inSharedCache() const {
            return (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS &&
                    objc::inSharedCache((uintptr_t)this));}
    };

    small &small() const {ASSERT(isSmall());
        return *(struct small *)((uintptr_t)this & ~(uintptr_t)1);}
};

方法以 method_t 结构保存,iOS14 以上该结构发生巨大变化。struct big 在 64 位的系统上会占用 24 字节,name、types、imp 分别占用 64 bit 大小,与之前一样。但是 struct small 占用 12 字节,name、types、imp 分别占用 32 bit 大小。

name、types、imp 分别指向方法的 名称、参数数据、函数指针,苹果考虑到镜像中的方法都是固定的,不会跑到其他镜像中去。其实不需要 64 位寻址的指针,只需要 32 位即可 (多余 32 位寻址,可执行文件在内存中要超过 4G)。small 结构里面的数据,都是相对地址偏移,不是内存中的具体位置。如果要还原,需要进行计算。

template <typename T>
struct RelativePointer: nocopy_t {
    int32_t offset;

    T get() const {if (offset == 0)
            return nullptr;
        uintptr_t base = (uintptr_t)&offset;
        uintptr_t signExtendedOffset = (uintptr_t)(intptr_t)offset;
        uintptr_t pointer = base + signExtendedOffset;
        return (T)pointer;
    }
};

3. 手动解析

现在从 mach-o 文件来看怎么一步步找到 method 信息。

__objc_classlist 节保存所有类的地址,以第一个 class 为例,地址为:0x010001a340

跳转到 0x010001a340 地址处,红框所包即 class 结构所保存的值。方法等信息保存在 class_data_bits_t 处,class_data_bits_t 的值为 0x01000187a8。

跳转到 0x01000187a8 地址处,里面保存的为 class_ro_t 结构。

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;

    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
};

对照结构体内容,可知 baseMethodList 的地址为 0x0100010c78。

前 64 位 分别保存 baseMethodList 的 flag 和 count,flag 判断后续保存的 method_t 结构是 struct big 还是 struct small,

如果是 big 的话,以此取三个 64 位值,就可以得到 函数名称、类型、函数地址。

struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };

smallMethodListFlag 值为 0x80000000, flag 值为 0x800000c0,进行 位与 运算,说明以下的 method_t 结构是 small 结构。

struct small {
        // The name field either refers to a selector (in the shared
        // cache)or a selref (everywhere else).
        RelativePointer<const void *> name;
        RelativePointer<const char *> types;
        RelativePointer<IMP> imp;

        bool inSharedCache() const {
            return (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS &&
                    objc::inSharedCache((uintptr_t)this));}
    };

template <typename T>
struct RelativePointer: nocopy_t {
    int32_t offset;

    T get() const {if (offset == 0)
            return nullptr;
        uintptr_t base = (uintptr_t)&offset;
        uintptr_t signExtendedOffset = (uintptr_t)(intptr_t)offset;
        uintptr_t pointer = base + signExtendedOffset;
        return (T)pointer;
    }
};
  • name

name 的值为 0x9398,对应到 RelativePointer 的源代码,offset 值为 0x9398。

源码需要取 offset 的地址,才能计算出真正 name 的地址。offset 的地址可以计算出为 0x0100010c80,baseMethodList 的地址 + sizeof(flag) + sizeof(count) = 0x0100010c78 + 0x4 + 0x4。

name 真正地址即为 (int)0x9398 + 0x0100010c80 = 0x10001A018

  • type

Type 保存函数的参数和返回值类型,同理可计算出 type 的真正地址:

(int)0x1ffa + 0x0100010c84 = 0x0100012c7e

  • imp

imp 是函数地址,同理可以用计算出:

(int)0xffff5fd8 + 0x0100010c88 = 0x0100006c60 (这里 0xffff5fd8 是负数)

0x03 恢复符号表

符号在符号表中用 nlist 结构体保存。

1. nlist 结构体

// Describes an entry in the symbol table. It’s declared in /usr/include/mach-o/nlist.h.
struct nlist
{
    union {
#ifndef __LP64__
                char *n_name;   /* for use when in-core */
#endif
        long  n_strx;
    } n_un;
    unsigned char n_type;
    unsigned char n_sect;
    short n_desc;

#ifdef __LP32__
      /*32 位中 4byte*/
    unsigned long n_value;
#else
      /*64 位中 8byte*/
    unsigned long long n_value; 
#endif

};

i. u_un(n_strx)

u_un 的 n_strx 代表该符号在字符串表中偏移。

假如 n_strx 值为 0x22,那么该符号名称字符串相当于文件的偏移为:stroff + 0x22,空串为偏移为 0,对应着 0x00。

ii. n_type

n_type 字段主要用来标识符号的种类。n_type 拥有 8 个 bit,分配如下。

/*
 * Symbols with a index into the string table of zero (n_un.n_strx == 0) are
 * defined to have a null, "", name.  Therefore all string indexes to non null
 * names must not have a zero string index.  This is bit historical information
 * that has never been well documented.
 */

/*
 * The n_type field really contains four fields:
 *  unsigned char N_STAB:3,
 *            N_PEXT:1,
 *            N_TYPE:3,
 *            N_EXT:1;
 * which are used via the following masks.
 */
#define N_STAB  0xe0  /* if any of these bits set, a symbolic debugging entry */
#define N_PEXT  0x10  /* private external symbol bit */
#define N_TYPE  0x0e  /* mask for the type bits */
#define N_EXT   0x01  /* external symbol bit, set for external symbols */

/*
 * Only symbolic debugging entries have some of the N_STAB bits set and if any
 * of these bits are set then it is a symbolic debugging entry (a stab).  In
 * which case then the values of the n_type field (the entire field) are given
 * in <mach-o/stab.h>
 */

/*
 * Values for N_TYPE bits of the n_type field.
 */
#define N_UNDF  0x0     /* undefined, n_sect == NO_SECT */
#define N_ABS   0x2     /* absolute, n_sect == NO_SECT */
#define N_SECT  0xe     /* defined in section number n_sect */
#define N_PBUD  0xc     /* prebound undefined (defined in a dylib) */
#define N_INDR  0xa     /* indirect */
  • bit[0:1] 是 N_EXT,表示是外部符号。
  • bit[1:4] 是 N_TYPE,表示符号类型。

分 N_UNDF 未定义、N_ABS 绝对地址、N_SECT 本地符号、N_PBUD 预绑定符号、N_INDR 同名符号几种类型。

  • bit[4:5] 是 N_PEXT,表示私有外部符号。
  • bit[5:8] 是 N_STAB,表示调试符号,具体定义在 /usr/include/mach-o/stab.h。
nlist_64 fields: n_value         n_type n_sect  n_desc  n_strx  
                    0000000100006944 0e     01      0000    00001bc4 -[ViewController locationManager:didUpdateLocations:]
                    00000001000077ac 0e     01      0000    00001bfa -[ViewController didReceiveMemoryWarning]
                    00000001000077f8 0e     01      0000    00001c24 -[ViewController locationManager:didUpdateHeading:]
                    0000000100007950 0e     01      0000    00001c58 -[ViewController writePermissionToFile:]

stab 类型

/*
 * Symbolic debugger symbols.  The comments give the conventional use for
 * 
 *  .stabs "n_name", n_type, n_sect, n_desc, n_value
 *
 * where n_type is the defined constant and not listed in the comment.  Other
 * fields not listed are zero. n_sect is the section ordinal the entry is
 * refering to.
 */
#define N_GSYM  0x20    /* global symbol: name,,NO_SECT,type,0 */
#define N_FNAME 0x22    /* procedure name (f77 kludge): name,,NO_SECT,0,0 */
#define N_FUN   0x24    /* procedure: name,,n_sect,linenumber,address */
#define N_STSYM 0x26    /* static symbol: name,,n_sect,type,address */
#define N_LCSYM 0x28    /* .lcomm symbol: name,,n_sect,type,address */
#define N_BNSYM 0x2e    /* begin nsect sym: 0,,n_sect,0,address */
#define N_AST   0x32    /* AST file path: name,,NO_SECT,0,0 */
#define N_OPT   0x3c    /* emitted with gcc2_compiled and in gcc source */
#define N_RSYM  0x40    /* register sym: name,,NO_SECT,type,register */
#define N_SLINE 0x44    /* src line: 0,,n_sect,linenumber,address */
#define N_ENSYM 0x4e    /* end nsect sym: 0,,n_sect,0,address */
#define N_SSYM  0x60    /* structure elt: name,,NO_SECT,type,struct_offset */
#define N_SO    0x64    /* source file name: name,,n_sect,0,address */
#define N_OSO   0x66    /* object file name: name,,0,0,st_mtime */
#define N_LSYM  0x80    /* local sym: name,,NO_SECT,type,offset */
#define N_BINCL 0x82    /* include file beginning: name,,NO_SECT,0,sum */
#define N_SOL   0x84    /* #included file name: name,,n_sect,0,address */
#define N_PARAMS  0x86  /* compiler parameters: name,,NO_SECT,0,0 */
#define N_VERSION 0x88  /* compiler version: name,,NO_SECT,0,0 */
#define N_OLEVEL  0x8A  /* compiler -O level: name,,NO_SECT,0,0 */
#define N_PSYM  0xa0    /* parameter: name,,NO_SECT,type,offset */
#define N_EINCL 0xa2    /* include file end: name,,NO_SECT,0,0 */
#define N_ENTRY 0xa4    /* alternate entry: name,,n_sect,linenumber,address */
#define N_LBRAC 0xc0    /* left bracket: 0,,NO_SECT,nesting level,address */
#define N_EXCL  0xc2    /* deleted include file: name,,NO_SECT,0,sum */
#define N_RBRAC 0xe0    /* right bracket: 0,,NO_SECT,nesting level,address */
#define N_BCOMM 0xe2    /* begin common: name,,NO_SECT,0,0 */
#define N_ECOMM 0xe4    /* end common: name,,n_sect,0,0 */
#define N_ECOML 0xe8    /* end common (local name): 0,,n_sect,0,address */
#define N_LENG  0xfe    /* second stab entry with length information */

/*
 * for the berkeley pascal compiler, pc(1):
 */
#define N_PC    0x30    /* global pascal symbol: name,,NO_SECT,subtype,line */

可以用 nm -a 查看所有符号,显示 stab 类型。

0000000100008414 - 01 0000   FUN -[ViewController .cxx_destruct]
0000000100008414 t -[ViewController .cxx_destruct]
0000000100006e64 t -[ViewController viewDidLoad]
0000000100006e64 - 01 0000   FUN -[ViewController viewDidLoad]
0000000100007cd4 t -[ViewController viewWillAppear:]
0000000100007cd4 - 01 0000   FUN -[ViewController viewWillAppear:]
0000000000000000 - 00 0000  GSYM _OBJC_CLASS_$_ViewController
000000010001a628 S _OBJC_CLASS_$_ViewController
0000000000000000 - 00 0000  GSYM _OBJC_METACLASS_$_ViewController
000000010001a650 S _OBJC_METACLASS_$_ViewController
0000000100018718 - 13 0000 STSYM __OBJC_$_INSTANCE_METHODS_ViewController
0000000100018718 s __OBJC_$_INSTANCE_METHODS_ViewController
0000000100018828 s __OBJC_$_INSTANCE_VARIABLES_ViewController
0000000100018828 - 13 0000 STSYM __OBJC_$_INSTANCE_VARIABLES_ViewController
0000000100018850 s __OBJC_$_PROP_LIST_ViewController
0000000100018850 - 13 0000 STSYM __OBJC_$_PROP_LIST_ViewController
00000001000186b0 s __OBJC_CLASS_PROTOCOLS_$_ViewController
00000001000186b0 - 13 0000 STSYM __OBJC_CLASS_PROTOCOLS_$_ViewController
00000001000188a8 - 13 0000 STSYM __OBJC_CLASS_RO_$_ViewController
00000001000188a8 s __OBJC_CLASS_RO_$_ViewController
00000001000186d0 s __OBJC_METACLASS_RO_$_ViewController
00000001000186d0 - 13 0000 STSYM __OBJC_METACLASS_RO_$_ViewController

iii. n_sect

section 索引,说明符号保存在哪一个 section 中,比如 -[ViewController .cxx_destruct] 保存在 TEXT 段text 节中,__text 节的索引为 1。

iv. n_desc

未定义符号和 weak 符号的类型等。链接相关。

v. n_value

随着符号的种类,也就是 n_type 值的不同,n_value 也有不一样的含义。 如果是 N_SECT 符号,n_value 是符号所在的地址。

0x04 patch 可执行文件

1. Mach-O 简介

Mach 则是一种操作系统内核,Mach 内核被 NeXT 公司的 NeXTSTEP 操作系统使用。在 Mach 上,一种可执行的文件格是就是 Mach-O(Mach Object file format)。

Mach-O 文件的格式如下图所示:

  • Header:保存了 Mach-O 的一些基本信息,包括了平台、文件类型、LoadCommands 的个数等等。
  • LoadCommands:这一段紧跟 Header,加载 Mach-O 文件时会使用这里的数据来确定内存的分布。
  • Data:每一个 segment 的具体数据都保存在这里,这里包含了具体的代码、数据等等。

2. patch 加载命令

所有 load_command 都有的数据结构为:

struct load_command {
    uint32_t cmd;       /* type of load command */
    uint32_t cmdsize;   /* total size of command in bytes */
};

cmd 字段代表当前加载命令的类型,cmdsize 字段代表当前加载命令的大小。

Load Commands 直接就跟在 Header 后面,所有 command 占用内存的总和在 Mach-O Header 里面已经给出了。

在加载过 Header 之后就是通过解析 LoadCommand 来加载接下来的数据了。

i. LC_SYMTAB

LC_SYMTAB 数据结构如下,保存着符号表以及字符串表在 dylib 文件中的偏移和大小。

struct symtab_command
{
     unsigned long cmd;
     unsigned long cmdsize;
     unsigned long symoff;
     unsigned long nsyms;
     unsigned long stroff;
     unsigned long strsize;
};

/*
cmd 以及 cmdsize 如上文所说:cmd 字段代表当前加载命令的类型,cmdsize 字段代表当前加载命令的大小

symoff: image 文件开头到符号表位置的字节偏移,符号表是 **nlist 结构体** 数组
nsyms: 符号个数
stroff: image 文件开头到字符串表位置的字节偏移
strsize: 字符串表所占大小
*/

获取符号表和字符表的偏移后,将恢复的符号信息添加到符号表里,将函数名的字符串添加到字符串表。

并且修改 LC_SYMTAB 结构中数据:

  • 符号的个数

一共新增 152 个符号

  • 字符串表偏移

152 * 16 + 126024 = 128456

increase_sym_num * sizeof(nlist) + orig_str_off = new_str_off

  • 字符串表的大小。

新增 152 个符号的名称,4696 + 5456 = 10152

orig_str_size + increase_str_size = new_str_size

ii. LC_DYSYMTAB

LC_DYSYMTAB 记录各种符号在符号表和动态符号表中的索引和个数,一共记录 9 种符号。

struct dysymtab_command {
    uint32_t cmd;   /* LC_DYSYMTAB */
    uint32_t cmdsize;   /* sizeof(struct dysymtab_command) */

    uint32_t ilocalsym; /* index to local symbols */
    uint32_t nlocalsym; /* number of local symbols */

    uint32_t iextdefsym;/* index to externally defined symbols */
    uint32_t nextdefsym;/* number of externally defined symbols */

    uint32_t iundefsym; /* index to undefined symbols */
    uint32_t nundefsym; /* number of undefined symbols */

    uint32_t tocoff;    /* file offset to table of contents */
    uint32_t ntoc;  /* number of entries in table of contents */

    uint32_t modtaboff; /* file offset to module table */
    uint32_t nmodtab;   /* number of module table entries */

    uint32_t extrefsymoff;  /* offset to referenced symbol table */
    uint32_t nextrefsyms;   /* number of referenced symbol table entries */

    uint32_t indirectsymoff; /* file offset to the indirect symbol table */
    uint32_t nindirectsyms;  /* number of indirect symbol table entries */

    uint32_t extreloff; /* offset to external relocation entries */
    uint32_t nextrel;   /* number of external relocation entries */

    uint32_t locreloff; /* offset to local relocation entries */
    uint32_t nlocrel;   /* number of local relocation entries */

};

新增本地符号 140 个,外部符号 12 个,一共 152 个。修改相应符号数据。

重定位项表偏移(indSym table offset)因为符号表和字符串表增大,所以也会增大。

125296 + 152 * 16 = 127728

Orig_indSym_offset + Increase_sym_num * sizeof(nlist) = new_indSym_offset

iii. LC_SEGMENT

表示一个段加载命令,需要将它加载到对应的进程空间中。

/*
 * The 64-bit segment load command indicates that a part of this file is to be
 * mapped into a 64-bit task's address space.  If the 64-bit segment has
 * sections then section_64 structures directly follow the 64-bit segment
 * command and their size is reflected in cmdsize.
 */
struct segment_command_64 { /* for 64-bit architectures */
    uint32_t    cmd;        /* LC_SEGMENT_64 */
    uint32_t    cmdsize;    /* includes sizeof section_64 structs */
    char        segname[16];    /* segment name */
    uint64_t    vmaddr;     /* memory address of this segment */
    uint64_t    vmsize;     /* memory size of this segment */
    uint64_t    fileoff;    /* file offset of this segment */
    uint64_t    filesize;   /* amount to map from the file */
    vm_prot_t   maxprot;    /* maximum VM protection */
    vm_prot_t   initprot;   /* initial VM protection */
    uint32_t    nsects;     /* number of sections in segment */
    uint32_t    flags;      /* flags */
};
  • segname 16 字节大小,用来存储段的名称
  • vmaddr 段要加载的虚拟内存的地址
  • vmsize 段所占的虚拟内存的大小
  • fileoff 段数据所在文件中的偏移位置
  • filesize 段数据实际的大小
  • maxprot 页面所需要的最高内存保护
  • initprot 页面初始的内存保护
  • nsects 段所包含的节区
  • flags 段的标志信息

__LINKEDIT 包含需要被动态链接器使用的信息,包括符号表、字符串表、重定位项表等。

__LINKEDITLC_SEGMENT 只需要修改段的大小:

36048 + 152 * 16 + 5456 = 43936

file_size + Increase_sym_num * sizeof(nlist) + increase_str_size = new_file_size

0x05 效果

恢复支付宝符号并打印调用栈:

      "0   AlipayWallet                        0x000000010a5e0660 +[AUNetworkInfo bssid] + 232",
      "1   AlipayWallet                        0x000000010a5dfe90 +[AUNetworkInfo networkInfo] + 160",
      "2   AlipayWallet                        0x000000010a5d99e0 +[AUDeviceInfo deviceInfo] + 132",
      "3   AlipayWallet                        0x000000010a5d8c10 +[AUDeviceInfo deviceInfoWithoutAsyncData] + 84",
      "4   AlipayWallet                        0x000000010a5d8934 +[AUDeviceInfo deviceInfoWithBlock:] + 224",
      "5   libdispatch.dylib                   0x000000018340a610 A3849F96-1C9F-36C5-A15F-70C566F14CFF + 374288",
      "6   libdispatch.dylib                   0x000000018340b184 A3849F96-1C9F-36C5-A15F-70C566F14CFF + 377220",
      "7   libdispatch.dylib                   0x00000001833e4b50 A3849F96-1C9F-36C5-A15F-70C566F14CFF + 219984",
      "8   libdispatch.dylib                   0x00000001833f1110 A3849F96-1C9F-36C5-A15F-70C566F14CFF + 270608",
      "9   libdispatch.dylib                   0x00000001833f18b0 A3849F96-1C9F-36C5-A15F-70C566F14CFF + 272560",
      "10  libsystem_pthread.dylib             0x000000018345ab48 _pthread_wqthread + 212",
      "11  libsystem_pthread.dylib             0x000000018345d760 start_wqthread + 8"

代码:https://github.com/HeiTanBc/restore-symbol

参考资料

  • http://blog.imjun.net/posts/restore-symbol-of-iOS-app/

  • https://juejin.cn/post/6844904133321818126#heading-17

  • https://opensource.apple.com/source/objc4/objc4-781/

  • https://opensource.apple.com/source/objc4/objc4-818.2/



https://mp.weixin.qq.com/s/nLpjaSD5G7XYl_RZbYMm7w

最多阅读

快速配置 Sign In with Apple 1年以前  |  4383次阅读
使用 GPUImage 实现一个简单相机 2年以前  |  3088次阅读
APP适配iOS11 2年以前  |  2989次阅读
开篇 关于iOS越狱开发 2年以前  |  2885次阅读
在越狱的iPhone设置上使用lldb调试 2年以前  |  2835次阅读
App Store 审核指南[2017年最新版本] 2年以前  |  2825次阅读
给数组NSMutableArray排序 2年以前  |  2788次阅读
所有iPhone设备尺寸汇总 2年以前  |  2744次阅读
UITableViewCell高亮效果实现 2年以前  |  2587次阅读
使用ssh访问越狱iPhone的两种方式 2年以前  |  2569次阅读
关于Xcode不能打印崩溃日志 2年以前  |  2433次阅读
使用ssh 访问越狱iPhone的两种方式 2年以前  |  2312次阅读
iOS14 隐私适配及部分解决方案 1年以前  |  2076次阅读
为对象添加一个释放时触发的block 2年以前  |  2066次阅读
使用最高权限操作iPhone手机 2年以前  |  2066次阅读

手机扫码阅读