iOS逆向学习笔记之–Match-O文件结构
Match-O是Mach object的缩写,是Mac/iOS上用于存储程序、库的标准格式,是苹果的可执行文件类型。
利用MachOView查看对应的可执行文件包括:
文件头 mach64 Header
加载命令 Load Commands
文本段 __TEXT
数据段 __DATA
动态库加载信息 Dynamic Loader Info
入口函数 Function Starts
符号表 Symbol Table
动态库符号表 Dynamic Symbol Table
字符串表 String Table
补充:每个段可以拥有零个或多个区域(section)。每一个段(segment)都拥有一段虚拟地址映射到进程的地址空间
Match-O的Header
利用otool工具查看Mach-o文件的头部
macdeiMac:haluo mac$ otool -hv EasyBike.decrypted Mach header magic cputype cpusubtype caps filetype ncmds sizeofcmds flags MH_MAGIC ARM V7 0x00 EXECUTE 59 6016 NOUNDEFS DYLDLINK TWOLEVEL WEAK_DEFINES BINDS_TO_WEAK PIE Mach header magic cputype cpusubtype caps filetype ncmds sizeofcmds flags MH_MAGIC_64 ARM64 ALL 0x00 EXECUTE 59 6744 NOUNDEFS DYLDLINK TWOLEVEL WEAK_DEFINES BINDS_TO_WEAK PIE // 头部的结构如下 struct mach_header { uint32_t magic; cpu_type_t cputype; cpu_subtype_t cpusubtype; uint32_t filetype; uint32_t ncmds; uint32_t sizeofcmds; uint32_t flags; }; struct mach_header_64 { uint32_t magic; cpu_type_t cputype; cpu_subtype_t cpusubtype; uint32_t filetype; uint32_t ncmds; uint32_t sizeofcmds; uint32_t flags; uint32_t reserved; };
- magic
魔数,系统加载器通过改字段快速,判断该文件是用于32位or64位。 //32位魔数 #define MH_MAGIC 0xfeedface #define MH_CIGAM 0xcefaedfe //64位魔数 #define MH_MAGIC_64 0xfeedfacf #define MH_CIGAM_64 0xcffaedfe
- CPU Type
CPU类型以及子类型字段,该字段确保系统可以将适合的二进制文件在当前架构下运行。 #define CPU_TYPE_ARM ((cpu_type_t) 12) #define CPU_TYPE_ARM64 (CPU_TYPE_ARM | CPU_ARCH_ABI64)
- CPU SubType
CPU指定子类型,对于inter,arm,powerpc等CPU架构,其都有各个阶段和等级的CPU芯片,该字段就是详细描述其支持CPU子类型 #define CPU_SUBTYPE_ARM_V7 ((cpu_subtype_t) 9) #define CPU_SUBTYPE_ARM64_ALL ((cpu_subtype_t) 0) #define CPU_SUBTYPE_ARM64_V8 ((cpu_subtype_t) 1)
- File Type
说明该mach-o文件类型(可执行文件,库文件,核心转储文件,内核扩展,DYSM文件,动态库等) #define MH_OBJECT 0x1 //.o目录文件 #define MH_EXECUTE 0x2 //a.out可主动执行文件 #define MH_DYLIB 0x6 //.dylib文件 #define MH_DSYM 0xa //.dSYM文件 #define MH_KEXT_BUNDLE 0xb //.kext驱动文件
Number of load Commands
表示加载命令条数Size of Load Commands
表示加载命令大小Flags
标志位,该字段用位表示二进制文件支持的功能,主要是和系统加载,链接相关 #define MH_NOUNDEFS 0x1 // 目前没有未定义的符号,不存在链接依赖 #define MH_DYLDLINK 0x4 // 该文件是dyld的输入文件,无法被再次静态链接 #define MH_PIE 0x200000 // 加载程序在随机的地址空间,只在 MH_EXECUTE中使用 #define MH_TWOLEVEL 0x80 // 两级名称空间
- Load Commands 加载命令
Load Commands的作用是告诉操作系统应当如何加载文件中的数据,对系统内核加载器和动态连接器起指导作用。
load command结构如下:
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};
// CMD字段含义:
LC_SEGMENT 将文件中(32位或64位)的段映射到进程地址空间中 // LC_SEGMENT_64
LC_SYMTAB 符号表地址
LC_DYSYMTAB 动态符号表地址
LC_DYLD_INFO_ONLY 动态链接相关信息
LC_LOAD_DYLINKER 加载一个动态链接器(动态库加载器),通常路径是“/usr/lib/dyld”
LC_LOAD_DYLIB 加载一个动态链接共享库。如“/usr/lib/libSystem.B.dylib”,这是C标准库。每个库由动态链接器加载并包含一个符号表
LC_UUID 文件的唯一标识,crash解析中也会有该值,去确定dysm文件和crash文件是匹配的
LC_VERSION_MIN_IPHONEOS 二进制文件要求的最低操作系统版本
LC_MAIN 设置程序主线程的入口地址和栈大小
LC_SOURCE_VERSION 构建该二进制文件使用的源代码版本
LC_FUNCTION_STARTS 定义一个函数起始地址表,使调试器和其他程序易于看到一个地址是否在函数内
LC_DATA_IN_CODE 定义在代码段内的非指令数据
a. LC_SEGMENT_64 和 LC_SEGMENT
加载的主要命令,它负责指导内核来设置进程的内存空间
snegment数据结构如下:
struct segment_command { /* for 32-bit architectures */
uint32_t cmd; /* LC_SEGMENT */
uint32_t cmdsize; /* includes sizeof section structs */
char segname[16]; /* segment name */
uint32_t vmaddr; /* memory address of this segment */
uint32_t vmsize; /* memory size of this segment */
uint32_t fileoff; /* file offset of this segment */
uint32_t filesize; /* amount to map from the file */
vm_prot_t maxprot; /* maximum VM protection */
vm_prot_t initprot; /* initial VM protection */
uint32_t nsects; /* number of sections in segment */
uint32_t flags; /* flags */
};
struct segment_command_64 { /* for 64-bit architectures */
uint32_t cmd; /* LC_SEGMENT_64 */
uint32_t cmdsize; /* includes sizeof section_64 structs */
char segname[16]; /* segment name */
uint64_t vmaddr; /* memory address of this segment */
uint64_t vmsize; /* memory size of this segment */
uint64_t fileoff; /* file offset of this segment */
uint64_t filesize; /* amount to map from the file */
vm_prot_t maxprot; /* maximum VM protection */
vm_prot_t initprot; /* initial VM protection */
uint32_t nsects; /* number of sections in segment */
uint32_t flags; /* flags */
};
cmd: 就是Load Command类型,这里LC_SEGMENT_64代表将文件中64位的段映射到进程的地址空间。LC_SEGMENT_64和LC_SEGMENT的结构差别不大
cmdsize: 代表Load commands的大小
segname: 16字节的段名称
vmaddr 段的虚拟内存起始地址
vmsize 段的虚拟内存大小
fileoff 段在文件中的偏移量
filesize 段在文件中的大小
maxprot 段页面的最高内存保护
initprot 初始内存保护
nsects segment的个数
flags 标志位
b. Segment 和 Section
“__TEXT”代表的是Segment, “__text”代表Section
__PAGEZERO:一个全用0填充的段,用于抓取空指针引用(非法内存访问)。这通常不会占用物理内存空
__TEXT: 本段只有可执行代码和其他只读数据
__text 主程序代码
__stub_helper 用于动态库链接的桩的辅助
__cstring 常量字符串符号表描述信息,通过该区信息,可以获得常量字符串符号表地址
__DATA:用于读取和写入数据的一个段
__nl_symbol_ptr 非延迟导入符号指针表。
__la_symbol_ptr 延迟导入符号指针表。
__LINKEDIT:包含给动态链接器的原始数据的段,包括符号和字符串表,压缩动态链接信息,以及动态符号表等
c. Section的数据结构
struct section { /* for 32-bit architectures */
char sectname[16]; /* name of this section */
char segname[16]; /* segment this section goes in */
uint32_t addr; /* memory address of this section */
uint32_t size; /* size in bytes of this section */
uint32_t offset; /* file offset of this section */
uint32_t align; /* section alignment (power of 2) */
uint32_t reloff; /* file offset of relocation entries */
uint32_t nreloc; /* number of relocation entries */
uint32_t flags; /* flags (section type and attributes)*/
uint32_t reserved1; /* reserved (for offset or index) */
uint32_t reserved2; /* reserved (for count or sizeof) */
};
struct section_64 { /* for 64-bit architectures */
char sectname[16]; /* name of this section */
char segname[16]; /* segment this section goes in */
uint64_t addr; /* memory address of this section */
uint64_t size; /* size in bytes of this section */
uint32_t offset; /* file offset of this section */
uint32_t align; /* section alignment (power of 2) */
uint32_t reloff; /* file offset of relocation entries */
uint32_t nreloc; /* number of relocation entries */
uint32_t flags; /* flags (section type and attributes)*/
uint32_t reserved1; /* reserved (for offset or index) */
uint32_t reserved2; /* reserved (for count or sizeof) */
uint32_t reserved3; /* reserved */
};
sectname:比如__text、__stubs
segname :该section所属的segment,比如__TEXT
addr : 该section在内存的起始位置
size: 该section的大小
offset: 该section的文件偏移
align : 字节大小对齐(以多少字节对齐,一般是2的乘幂)
reloff :重定位入口的文件偏移
nreloc: 需要重定位的入口数量
flags:包含section的type和attributes
reserved: 预留的字段
d. __TEXT段包含的信息
__text 程序可执行的代码区域
__stubs 间接符号存根,跳转到懒加载指针表
__sub_helper 帮助解决懒加载符号加载的辅助函数
__cstring 只读的C风格字符串,包含OC的部分字符串和属性名
__objc_methname 方法名
__objc_classname 类名
__objc_methtype 方法签名
e. __DATA段包含的信息
__la_symbol_ptr 非懒加载指针表,在dyld加载时会立即绑定值
__mod_init_func 初始化函数
__objc_classlist 类列表
__objc_nlclslist 程序中自己实现了+load方法的类
__objc_protolist 协议列表
__objc_classrefs 被引用的类
__objc_ivar 成员变量
__got 非懒加载全局指针表
- 动态库链接信息 LC_DYLD_INFO_ONLY
根据该加载命令的字段偏移,可以得到压缩动态数据信息区(动态库绑定,地址重定向等信息)
struct dyld_info_command {
uint32_t cmd; /* LC_DYLD_INFO or LC_DYLD_INFO_ONLY */
uint32_t cmdsize; /* sizeof(struct dyld_info_command) */
uint32_t rebase_off; /* file offset to rebase info */
uint32_t rebase_size; /* size of rebase info */
uint32_t bind_off; /* file offset to binding info */
uint32_t bind_size; /* size of binding info */
uint32_t weak_bind_off; /* file offset to weak binding info */
uint32_t weak_bind_size; /* size of weak binding info */
uint32_t lazy_bind_off; /* file offset to lazy binding info */
uint32_t lazy_bind_size; /* size of lazy binding infs */
uint32_t export_off; /* file offset to lazy binding info */
uint32_t export_size; /* size of lazy binding infs */
};
rebase_off 重定向数据的偏移地址
rebase_size 重定向数据的大小
bind_off 数据绑定
bind_size 数据绑定信息大小
weak_bind 弱绑定
lazy_bind 延时绑定数据
export 对外开放的函数信息