iOS逆向系列-Mach-O文件

概述

Mach-O是Mach object的缩写,是MaciOS上用于存储程序、库的标准格式。

常见的Mach-O文件

属于Mach-O格式的文件类型有。

可以在xnu源码中,查看到Mach-O格式的详细定义(https://opensource.apple.com/tarballs/xnu/)

  • MH_OBJECT
    代码编译中间的产物目标文件(.o)属于MH_OBJECT类型Mach-O格式文件。我们平时制作的静态库本质就是N个.O文件的集合,所以静态库也是属于MH_OBJECT类型Mach-O格式文件。
  • MH_EXECUTE
    顾名思义是可执行文件,通过项目编译的二进制文件就是属于MH_EXECUTE类型的Mach-O格式文件。
  • MH_EXECUTE
    苹果的动态库比如.Framework、.dylib文件是属于MH_EXECUTE类型的Mach-O格式文件。
  • MH_DYLINKER
    动态库链接编译器 比如iPhone目录/usr/lib/dyld属于MH_DYLINKER类型的Mach-O格式文件。
  • MH_DSYM
    存储着二进制文件符号信息的文件。项目编译生成的不仅有程序还有一个与之对应的.dsYM文件。

Mach-O的基本结构

[官方描述(https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/MachOTopics/0-Introduction/introduction.html)
一个Mach-O文件包含3个主要区域

  • Header
    文件类型、目标架构类型等
  • Load commands
    描述文件在虚拟内存中的逻辑结构、布局
  • Raw segment data
    在Load commands中定义的Segment的原始数据

窥探Mach-O的基本结构

  • 命令行file指令查看

    • file 文件路径
  • otool查看Mach-O特定部分和段的内容
    通过otool -l 可执行文件路径 快速查看Mach-O基础结构信息,比如通过cryptid的值可以判断是否加壳。

  • 命令行lipo指令

    • 查看架构信息:lipo -info 文件路径
    • 导出某种特定架构:lipo 文件路径 -thin 架构类型 -output 输出文件路径
    • 合并多种架构:lipo 文件路径1 文件路径2 -output 输出文件路径

Universal Binary

翻译为通用的二进制文件,同时支持多种架构的二进制文件。因为需要储存多种架构的代码,通用二进制文件通常比单一平台二进制的程序要大。
由于两种架构有共同的一些资源,所以并不会达到单一版本的两倍之多,由于执行过程中,只调用一部分代码,运行起来也不需要额外的内存。因为文件比原来的要大,也被称为“胖二进制文件”(Fat Binary)。

通过xcode编译的静态库需要支持多架构的配置:

不同的CPU架构不同 指令集不同 二进制指令跟汇编指令一一对应,处理器比较高级,指令集就多,支持的功能就不比较多。

v6架构支持的机型
iPhone、iPhone3G、iPod Touch、iPod Touch2

v7架构支持的机型
iPhone3GS、iPhone4、iPhone4S、
iPad、iPad2、iPad3
iPad mini
iPod Touch3G、iPod Touch4、iPod Touch5

v7s
iPhone5、iPhone5C
iPad2

arm64
iPhone5s、iPhone6、iPhone6 Plus、iPhone6s、iPhone6s Plus
iPhoneSE、iPhone7、iPhone7 Plus、iPhone8 Plus、iPhone8 Plus、iPhoneX
iPad5、iPad Air、iPad Air2、iPad Pro、iPad Pro2
iPad mini with Retina display、iPad mini3、iPad mini4
iPod Touch6

动态库共享缓存(dyld shared cache)

在我们手机的/usr/lib目录下有个Mac-O文件dyld。

dyld是可以加载苹果的动态库。从iOS3.1开始,为了提高性能,绝大部分的系统动态库文件都打包存放到了一个缓存文件中(dyld shared cache)。

缓存文件路径:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_armX

dyld源码 https://opensource.apple.com/tarballs/dyld/。在最新的源码版本当中我们可以使用源码中的launch-cache/dsc_extractor.cpp抽取缓存文件中的动态库。

从缓存动态共享库中抽取动态库

  • 将dyld的源码中的dsc_extractor.cpp的#if 0一上代码删除,包含#if 0与#endif,代码如下
#include <stdio.h>
#include <stddef.h>
#include <dlfcn.h>
typedef int (*extractor_proc)(const char* shared_cache_file_path, const char* extraction_root_path,
													void (^progress)(unsigned current, unsigned total));
int main(int argc, const char* argv[])
{
	if ( argc != 3 ) {
		fprintf(stderr, "usage: dsc_extractor <path-to-cache-file> <path-to-device-dir>
");
		return 1;
	}
	
	//void* handle = dlopen("/Volumes/my/src/dyld/build/Debug/dsc_extractor.bundle", RTLD_LAZY);
	void* handle = dlopen("/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/usr/lib/dsc_extractor.bundle", RTLD_LAZY);
	if ( handle == NULL ) {
		fprintf(stderr, "dsc_extractor.bundle could not be loaded
");
		return 1;
	}
	
	extractor_proc proc = (extractor_proc)dlsym(handle, "dyld_shared_cache_extract_dylibs_progress");
	if ( proc == NULL ) {
		fprintf(stderr, "dsc_extractor.bundle did not have dyld_shared_cache_extract_dylibs_progress symbol
");
		return 1;
	}
	
	int result = (*proc)(argv[1], argv[2], ^(unsigned c, unsigned total) { printf("%d/%d
", c, total); } );
	fprintf(stderr, "dyld_shared_cache_extract_dylibs_progress() => %d
", result);
	return 0;
}
  • 命令行clang编译连接可执行文件
    • clang++ -o 执行文件名称 cpp源文件
  • 通过生成的可执行文件抽取

抽离的苹果的动态库就在我们执行的framework目录下

我们就可以获取苹果所有动态库代码的Mach-O文件,就可以通过上面的Hopper等工具做源码分析了。

dyld与Mach-O

dyld文件是属于在unix内核规范中的Mach-O格式的文件。dyld可以用来加载一下类型的Mach-O文件

  • MH_EXECUTE
  • MH_DYLIB
  • MH_BUNDLE

总结:App的可执行文件、动态库都是由dyld负责加载的。

原文地址:https://www.cnblogs.com/CoderHong/p/8952262.html