1、概念Mach-O、image、虚拟内存
Mach-O
是 mach object 的缩写,在 -objc解决分类不加载的问题的官方文档中,明确指出所有的源文件都会被转化成一个 objcet,只不过最后经过链接操作,工程或被转化成静态库、动态库或者是可执行文件(类型不同的 mach-O)。Mac内核可以直接读取的可执行文件只此一种。
Mach-O 文件分为三大部分:
mach-header;//文件格式、文件类型、CPU构架等
load commands;//程序怎么加载到内存的一些描述信息(文件布局、链接信息、符号表位置等)
segment and section;//真正存储数据或代码的地方
//segment 中的数据都会被印射到虚拟内存中,所以 segment 是按页对齐的。即:segment 中的数据在虚拟内存中占得大小要比在磁盘中所占大小更大。
image
- 每个二进制文件在内存空间的映射,每个二进制文件(framwork/dylib)都对应一个image。可通过断点image list查看。
- 一个二进制文件可以依赖其他库(三方或系统),一个运行中的app包含很多image,MachO文件包含了主image的程序主体+其他库的引用信息(name、version、path等),可通过MachOView查看验证。
- image、macho、二进制文件是一一对应的。
虚拟内存
二进制文件加载到内存后,会映射到虚拟的地址空间,因为segment内存对齐原因,虚拟大小>=文件大小。堆栈地址是app运行时的地址,是虚拟内存地址,每次都会变。
详见:Mach-O文件结构分析
2、fishhook原理分析
image起始地址
slide:每个image都对应一个不同的ALSR
mach_header:是 image 在虚拟内存中的初始地址
vmaddr_slide:是 image 在虚拟内存中的偏移
//主工程image: vmaddr_slide = slide + __PAGEZERO 的 size(一般为一页0x100000000),mach_header = slide
//非主工程image:vmaddr_slide = mach_header = slide
segment偏移地址
vmaddr:是 segment 初始位置在虚拟内存中相对于 image 初始位置的偏移(不包含slide)
fileoff:是 segment 的起始位置相对于文件起始位置的偏移;
// 两者都表示偏移地址,区别是一个是虚拟内存,一个是磁盘文件
hook原理
将懒加载和非懒加载表中符号的指向替换为自己的符号地址。
符号表中记录了所有的符号,静态依赖库的符号会被直接拷贝进入到主工程,生成最终的 mach-O 文件;
而依赖的动态库源码不会被拷贝到主工程中,之所以叫做动态库,是因为程序被加载时才进行链接,准确来说在 dyld2 中,是在链接主程序时才加载依赖的动态库;
符号表中存储全量符号,而动态库的符号额外存储一份在重定向表中,为了节约内存,表中只存储该符号在符号表中的 index;
重定向表分组排序,依次印射到 __stub、懒加载表(__la_symbol_ptr)、非懒加载表(__nl_symbol_ptr、__got),这一步在静态编译时期就完成了;
懒加载符号的调用在静态编译时期就被替换成了桩函数,桩函数只管取出懒加载符号表中的函数指针进行跳转;
懒加载符号表中的函数指针初始化(静态编译时期)时指向 __stub_helper 进而指向 binder 函数;
binder 函数在符号于运行时第一次被调用时进行寻址,然后替换懒加载符号表中的函数指针为真实的函数地址;
非懒加载符号表中的指针初始化时值为 0,动态链接之后立马进行寻址,寻址完成后进行替换;
fishhook 的原理总结起来就是一句话:将懒加载和非懒加载表中符号的指向替换为自己的符号地址。
3、疑问
1.自己定义的C方法在哪?
__DATA/TEXT,__objc:OC的类和方法等
__DATA,__mod_init_func:C/C++ 构造函数和非基本类型的 C++ 静态全局变量
__DATA,__la_symbol_ptr:系统C方法
2.fishhook 只能 hook 系统库函数,不能 hook 用户自定义的C函数?
网友评论