转自自己的博客 3c9K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3u0D9L8$3N6Q4x3X3g2A6L8h3A6#2L8W2)9J5k6h3&6W2N6q4)9J5c8Y4m8G2M7%4c8K6i4K6u0r3j5$3!0F1N6X3g2J5N6q4)9J5k6r3W2a6f1#2)9J5k6r3q4H3M7q4)9J5k6s2c8G2i4K6u0V1k6s2W2F1j5h3#2A6j5#2)9J5k6r3I4A6j5Y4u0S2M7Y4W2Q4x3V1k6Q4c8f1k6Q4b7V1y4Q4z5p5y4Q4c8e0g2Q4z5p5g2Q4b7V1u0Q4c8e0g2Q4b7U0W2Q4b7U0c8Q4c8e0N6Q4z5f1q4Q4z5o6c8Q4c8e0k6Q4z5e0k6Q4z5o6N6Q4c8e0N6Q4b7f1u0Q4b7e0m8Q4c8f1k6Q4b7V1y4Q4z5p5y4Q4c8e0k6Q4z5f1c8Q4b7e0g2Q4c8e0N6Q4z5f1y4Q4z5p5u0Q4c8e0W2Q4z5f1u0Q4b7f1q4Q4c8e0N6Q4z5e0g2Q4z5e0W2Q4c8e0c8Q4b7U0S2Q4b7f1q4Q4c8e0g2Q4b7V1q4Q4z5e0f1`.
本文会介绍一个自己写的工具,能够把第三方iOS应用转成动态库,并加载到自己的App中,文章最后会以支付宝为例,展示如何调用其中的C函数和OC方法。
工具开源地址:
5d3K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6@1L8$3u0W2k6Y4g2@1N6i4u0W2M7W2)9J5c8X3q4H3M7o6u0V1P5h3I4A6j5R3`.`.
为什么要把第三方应用转成动态库呢?与一般的注入动态库+重签名打包的手段有什么不一样呢?
好处主要有下面几点:
可以直接调用别人的算法
逆向分析别人的应用时,可能会遇到一些私有算法,如果搞不定的话,直接拿来用就好。
掌控程序的控制权
程序的主体是自己的App,第三方应用的代码只是以动态库的形式加载,主要的控制权还是在我们自己手里,所以可以直接绕过应用的检测代码(文章最后有关于这部分攻防的讨论)。
同个进程内加载多个应用
重签名打包毕竟只能是原来的应用,但是如果是动态库的话,可以同时加载多个应用到进程内了,比如你想同时把美图秀秀和饿了么加载进来也是可以的(秀秀不饿,想想去年大众点评那个APPmixer的软广 - -! )。
我们要把应用转成动态库,首先要知道这两者之前有什么相同与不同,有相同的才存在转换的可能,而不同之处就是我们要重点关注的了。

可执行文件和动态库都是标准的 Mach-O 文件格式,两者的文件头部结构非常类似,特别是其中的代码段(TEXT),和数据段(DATA)结构完全一致,这也是后面转换工作的基础。
不同点就是我们转换工作的重点了,主要有:
头部的文件类型
一个是 MH_EXECUTE 可执行文件, 一个是 MH_DYLIB 动态库, 还有各种头部的Flags,要特别留意下可执行文件中Flags部分的 MH_PIE 标志,后面再详细说。

动态库文件中多一个类型为 LC_ID_DYLIB 的 Load Command, 作用是动态库的标识符,一般为文件路径。路径可以随便填,但是这部分必须要有,是codesign的要求。

可执行文件会多出一个 PAGEZERO段,动态库中没有。这个段开始地址为0(NULL指针指向的位置),是一个不可读、不可写、不可执行的空间,能够在空指针访问时抛出异常。这个段的大小,32位上是0x4000,64位上是4G。这个段的处理也是转换工作的重点之一,之前有人尝试转换,不成功就是因为没有处理好 PAGEZERO.

第一步是修改文件的头部信息,把文件类型从可执行文件修改成动态库,同时把一些Flags修改好。
这里一个比较关键的Flag是可执行文件中的 MH_PIE 标志位,(position-independent executable)。
这个标志位,表明可执行文件能够在内存中任意位置正确地运行,而不受其绝对地址影响的特性,这一特性是动态库所必须的一个特性。没有这个标志位的可执行文件是没有办法转换成动态库的。iOS系统中,arm64架构下,目前这个标志位是必须的,不然程序无法运行(系统的安全性要求),但是armv7架构下,可以没有这个标志位,所以支付宝armv7版本的可执行文件是不能转成动态库的,就是这个原因。不过所有的arm64的应用都是可以转换的,后面演示时用的支付宝是arm64架构的。
直接在文件头部中按照文档格式插入一个Load Command,并填入合适的数据。这里要注意下插入内容的字节数必须是8字节对齐的。
这部分是最重要的一部分,因为arm64上这个段的大小有4G,直接往内存中加载,会提示没有足够的连续的地址空间,所以必须要调整这个段的大小,而要调整 PAGEZERO 这个段的大小, 又会引起一连串的地址空间的变化,所以不能盲目的直接改,必须结合dyld的源码来对应修改。(注意这里不能直接把 PAGEZERO 这个段给去掉,也不能直接把大小调成0,因为涉及到dyld的rebase操作,详细看后面)
单纯减少 PAGEZERO 段的占用空间,作用不大,因为dyld加载动态库的时候,要求是所有的段一起进行mmap(详细可以查看dyld源码的ImageLoaderMachO::assignSegmentAddresses函数),所以必须把接下来所有的段的地址都重新计算一次。
同时要保证,前后两个段没有地址空间重叠,并且每个段都是按0x4000对齐。因为 PAGEZERO 是所有段中的第一个,所以可以直接把 PAGEZERO 的大小调整到0x4000,然后后面每一个段都按顺序依次减少同样大小(0xFFFFC000 = 0x100000000 - 0x4000),同时能保证每个段在文件内的偏移量不变。
修改前:

修改后:

这里的rebase是系统为了解决动态库虚拟内存地址冲突,在加载动态库时进行的基地址重定位操作。
这一步操作是整个流程里最重要的,因为按照前面的操作,整个文件地址空间已经发生了变化,如果dyld依然按照原来的地址进行rebase,必然会失败。
那么rebase操作需要做哪些工作呢?
相关的信息储存在 Mach-O 文件的 LINKEDIT 段中, 并由 LC_DYLD_INFO_ONLY 指定 rebase info 在文件中的偏移量

详细的rebase信息:

红框里那些Pointer的意思是说,在内存地址为 0x367C698 的地方有一个指针,这个指针需要进行rebase操作, 操作的内容就是和前面调整地址空间一样,每个指针减去 0xFFFFC000。

这个原因要涉及到文件中rebase信息的储存格式,上面的图中,可以看出rebase要处理的是一个个指针,但是实际上这些信息在文件中并不是以指针数组的形式存在,而是以一连串rebase opcode的形式存在,上面看到的一个个指针其实是 Mach O View 这个软件帮我们将opcode整理得到的。

这些opcode中有一种操作比较关键,REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB。

[培训]传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!