iOS 恢复调用栈(适配iOS14)

发表于 3年以前  | 总阅读数:1951 次

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/


本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/nLpjaSD5G7XYl_RZbYMm7w

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
快速配置 Sign In with Apple 5年以前  |  7734次阅读
使用 GPUImage 实现一个简单相机 5年以前  |  6024次阅读
APP适配iOS11 5年以前  |  5790次阅读
 目录