iOS安全:LC_LOAD_DYLIB和LC_ID_DYLIB / 2019-05-22

本文主要从iOS Mach-O的角度讲解了iOS 主APP的二进制和动态库的调用关系,涉及到LC_LOAD_DYLIB和LC_ID_DYLIB两种类型Load Command的含义及作用。

LC_LOAD_DYLIB

可执行文件(MH_EXECUTE类型)的Mach-O都会存在LC_LOAD_DYLIB类型的Load Command,该Load Command指定了当前Mach-O需要依赖的动态库(可以是系统的动态库也可以是开发者创建的动态库)。LC_LOAD_DYLIB类型的Load Command在内存中对应struct dylib_command结构,dylib_command结构包含struct dylib结构,struct dylib结构中name字段标记了动态库的路径。那么这个路径是从哪里来的呢?

LC_ID_DYLIB

动态库的Mach-O是MH_DYLIB类型的,一个动态库中必须包含一个LC_ID_DYLIB类型的Load Command(一般位于__LINKEDIT之后),它在内存中也是一个struct dylib_command结构,会有name字段。这里的dylib_command信息会在LINK时作为LC_LOAD_DYLIB类型的Load Command插入进可执行文件(MH_EXECUTE 类型的Mach-O)中。

打开APP时,dyld会先递归遍历所有类型为LC_LOAD_DYLIB的Load Command从而查找依赖库,查找的路径即是由对应的dylib_command结构的name指定,一般为@rpath/DYNAME.framework/DYNAME。如果在指定各种查找路径都找不到,就会出现"dyld: Library not loaded"错误。

修改LC_ID_DYLIB的name

根据上述原理,既然动态库的LC_ID_DYLIB仅仅标记了动态库的访问路径,那么可否任意修改?答案是肯定的,Xcode 中即提供了支持。

在Xcode中打开动态库工程,Build Settings中的Dynamic Library Install Name Base(默认为@rpath)及Dynamic Library Install Name(默认为$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH))会影响到LC_ID_DYLIB的name信息。这是由@rpath和LC_RPATH决定的:在APP Mach-O中的LC_RPATH指定了当前APP的@rpath环境变量的值,默认为Frameworks,动态库默认会被拷贝到APP的Frameworks目录,

但是如果直接修改动态库工程的Dynamic Library Install Name,很可能会出现"dyld: Library not loaded"错误。一个可供参考的方案如下:

  • 假设动态库实际名称为Dynamic,先修改动态库工程的Dynamic Library Install Name,假设改为“MYDYNAMIC”;
  • 打开主工程,引入动态库Dynamic.framework后,在Build Phases最后添加Run Script,脚本内容为:
APP_PATH="${TARGET_BUILD_DIR}/${WRAPPER_NAME}"
cd ${APP_PATH}
install_name_tool -change MYDYNAMIC @rpath/Dynamic.framework/Dynamic  ${PRODUCT_NAME}
#install_name_tool的用法为:
#install_name_tool -change (原dylib的LC_ID_DYLIB值) (设置的调用二进制LC_LOAD_DYLIB的值) (dylib名称)

意思就是在主工程产出主二进制之后,修改名称为MYDYNAMIC的LC_LOAD_DYLIB为@rpath/Dynamic.framework/Dynamic。

实际上,dyld在工作时,只关心去哪里找到需要加载的动态库,而动态库本身的LC_ID_DYLIB在此时意义已经不大。

其它文章

iOS安全:Mach-O Type
iOS安全:修改Mach-O
iOS安全:iOS APP注入动态库重打包(非越狱)
iOS安全:使用frida-ios-dump砸壳
iOS安全:使用dumpdecrypted/Clutch砸壳