iOS 恢复调用栈(适配iOS14)

共 23449字,需浏览 47分钟

 ·

2021-06-06 19:13

作者 | heitanBC 
来源 | iOS'RE, 点击阅读原文查看作者更多文章

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 包含需要被动态链接器使用的信息,包括符号表、字符串表、重定位项表等。

__LINKEDIT 的 LC_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/



推荐阅读

☞  为 iPad 部署基于 VS Code 的远程开发环境
☞  “And away we code. ” WWDC21 超 200 个 Session 等着你
☞  Google 正式发布 Fuchsia OS,Flutter 集成尚存问题
☞  京东APP订单业务Swift优化总结


就差您点一下了 👇👇👇

浏览 106
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报