likes
comments
collection
share

iOS 关于符号该知道的事儿

作者站长头像
站长
· 阅读数 28

符号表是什么

符号表 debugger Symbols,是Xcode编译项目生成的.dSYM文件

通俗点说,我们定义的方法和变量都有名称,保存这些名称的表就是符号表。

符号绑定及其过程

ASLD和重定向

image是镜像,本地编译完的可执行文件运行到内存后就是镜像。UIKitFoundation等动态库的可执行文件拷贝到内存就是镜像文件。

断点输出image list指令可以查看项目可执行文件的首地址 iOS 关于符号该知道的事儿

每次machO运行的时候,这个首地址位置都不一样,原因是系统会在虚拟地址前增加一个随机偏移量(ASLR

通过符号的地址和这个首地址相减,可以得到一个偏移,这个偏移值无论重新运行多少次不会改变。

接下来我们来说一下什么是重定向:ASLR + 偏移值 = 内存值

  • 内部函数地址通过重定向可以计算

  • 外部函数(NSLog等动态库函数)无法计算函数地址

符号的加载时机

DYLD动态链接器加载imageMachOview里面看到的Load Commands就是工程依赖库的基本信息,里面能看到很多系统库。加载完依赖库以后才会加载应用。

符号表使用了PIC技术,就是位置代码独立。首次启动应用但还未调用NSLog方法时,我们依旧可以在Dynamic Symbol tableIndirect Symbols里面能看到NSLog及其一些信息,但是这个时候其实还未进行符号绑定,里面记录的也不是真实的函数地址。

直到首次使用该方法时DYLD才会去做符号绑定(告诉当前应用这个函数的真实地址),下次就可以直接访问符号表进行调用。

这个就是符号的懒加载

符号绑定的流程

每个外部方法调用,首先要找到外部符号的桩。就是在symbol pointer stubs表找到存储的数据,这里存储的是跳转指令及传参。

接下来就需要去懒加载表(Lazy Symbol Pointers)里查找方法的相关信息。

如果是首次调用该方法,懒加载表记录的是Non Lazy Symbol Pointersdyld_stub_binder的方法地及第一步的传参。

dyld_stub_binder会到动态库里获取真实的方法地址,替换掉懒加载表的数据然后调用。

后续的每次调用就可以直接通过已绑定成功的函数地址调用函数,不需要再次绑定。

怎么去除符号

为什么要去除符号

去除符号后安装包会变小,应用更加安全

符号分几种

  • 全局符号(导出符号),一般提供给外部用,做为动态库用。所以动态库不能全部去掉符号。
  • 间接符号(导入符号),从Foundation等动态库过来的函数,需要做符号绑定的,所以不可以去掉

APP自己的方法一般不需要提供外部使用,所以除了间接符号,其他符号可以去除。

作用于本文件,static修饰函数的符号是本地符号。

作用于本文件,没有static的函数,可以提供给其他文件调用的,就是全局符号。

本地符号如果不使用,符号不会生成。

全局符号不使用也会生成符号。

通过objcdump –macho -t symboldemo1指令可以查看符号表

  • 前缀 l = local,本地符号

  • 前缀 g = gobal,全局符号

Xcode相关的配置

通过Xcode的设置可以控制我们需要保留在符号表的符号

Build Setting -> Target -> Deployment postprocessing 设置为NO编译时保留符号,打包的时候才去除。

Build Setting -> strip Style

  • All Symbol 一般APP用,只剩下间接符号
  • Non-Global Symbol,一般给动态库用,只剩全局符号
  • Debugging Symbol,编译时会做去符号操作,此时debug也无法断点

怎么恢复符号

逆向工程会有恢复符号的技术,什么能恢复符号呢?

OC有动态语言的特性,runtime可以通过字符串获得类、方法……

上述变量明即便设置了去除符号,在MachO文件中依旧会以字符串方式保存。

使用restore_symbol工具(搜索下载,找不到留言),可以恢复OCswift的本地符号。

  • swift属于动态派发所以可以恢复

  • OC有动态语言特性,所以可以恢复

  • C函数没有动态特性所以不可以恢复

怎么修改符号

fishhook框架

fishHookFacebook 提供的一个动态修改链接 mach-O 文件的工具

通过 修改符号表的指向 实现 勾起方法 的效果

代码很简单,fishHook 提供了两个钩子方法及一个钩子结构体,

一般如下构建rebinding结构体传入rebind_symbols函数即可

- (void)viewDidLoad {
    [super viewDidLoad];
    // hook NSLog
    struct rebinding nslog = {};
    // 方法名
    nslog.name = "NSLog";
    // 替换进去的方法
    nslog.replacement = new_NSLog;
    // 被替换的方法指针
    nslog.replaced = (void *)&sys_NSlog;
    // 创建结构体数组
    struct rebinding rbs[1] = {nslog};
    // hook
    rebind_symbols(rbs, 1);
}

// 系统NSLog的函数地址
static void (*sys_NSlog)(NSString *format, ...);

// 定义新的NSLog
void new_NSLog(NSString *format, ...) {
    format = [format stringByAppendingString:@"\n hook Succ"];
    sys_NSlog(format);
}

//点击一下 就输出
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"test");
}

fishhook的流程

fishhook 是通过重新绑定符号实现功能的,首先我们看一下怎么利用符号找到方法的名字

懒加载符号表(Lazy Symbol Pointers)和间接符号表(Indirect Symbols)的记录顺序是一致的。

在间接符号表(Lazy Symbol Pointers)的Data段,记录着这个符号在总符号表(Symbols)的编号。

根据编号查找总符号表,可以看到方法所属的库(Library)以及在字符串表(String Table)记录的偏移(string table index)。

到字符串表里面使用string table index加上字符串表第一行的pFile地址得出对应行,就能找到对应函数名。

那么其实只要反向操作上述过程,就可以通过方法名找到符号所在位置!

  • 1、fishhook在字符串表(String Table)使用.分割字符串,对比传入的方法名找出所在行,再用所在行减去首行pFile算出string table index

  • 2、在总符号表(String Table)遍历对比string table index 查出符号在总符号表的位置(symbols index

  • 3、在间接符号表(Indirect Symbols)用symbols index 查找该符号偏移值(indirect symbols index

  • 4、通过符号在懒加载符号表(Lazy Symbol Pointers)和间接符号表(Indirect Symbols)位置一一对应的关系就可以就找到符号在懒加载符号表的位置。

  • 5、最终修改的值是懒加载符号表(Lazy Symbol Pointers)里记录的值

后续调用方法的时候先找桩,找到桩就会找lazy symbol pointer执行,此时找到的就是修改后的方法地址了。

解决第三方库的符号冲突

动态库的情况

当两个动态库都有同名方法的时候,动态库方法的调用是先找到方法所在库,再去找方法符号。

所以找到了A库就不会到B库找符号。动态库之间不存在符号冲突

静态库的情况

两个静态库都有同名方法的时候

  • 1、如果静态库没有被使用到,就不会被链接,其链接的最小单位是类
  • 2、链接器链接到A符号之后就不会再链接同名的A符号了

如果静态库里面添加分类,那情况就会不一样了。分类是动态添加的,分类在编译时不会被链接。

静态库链接的时候也不确定动态库是否使用分类代码,所以不会链接分类,运行就会崩溃找不到该方法。

所以解除上述问题的办法就是添加Other Link Flags添加-ObjC参数,这样编译的时候就会连同分类里面的代码把所有OC代码编译进去。

两个静态库都有分类的时候,可以通过Build Setting -> Other Link Flags参数使用-forceload指定库加载代码,以解决冲突。

碰到两个静态库都有动态代码需要链接的时候,又不提供源码的时候,使用llvmobjcopy修改库的二进制文件添加前缀以解决冲突。

转载自:https://juejin.cn/post/7051507256470274056
评论
请登录