OC底层MachO结构正向与逆向以及虚拟内存加载

Mach-O文件由三部分组成,从地址0x100000000开始(在arm64架构中,__PAGEZERO段的终止位置是从0x100000000(8个0)而在非arm64架构中,__PAGEZERO段的终止位置是从0x4000(3个0)开始)

一、MachO Header :用于快速确认该文件的CPU类型、文件类型等信息

// loader.h
struct mach_header_64 {
     uint32_t  magic; // 代表当前Mach-O文件的架构 0xFEEDFACE表示32位二进制,0xFEEDFACF表示64位二进制
     cpu_type_t cputype; // 标记CPU类型(machine.h) uint32_t的枚举大小
     cpu_subtype_t cpusubtype; // 标记CPU具体类型(machine.h)
     uint32_t  filetype;   // 文件的类型(可执行文件、库文件、核心转储文件、内核扩展等)
     uint32_t  ncmds;      // 用手加教器的“加裁命今”的条数和大小 load commands数量 = 所有segment数量+__PAGEZERO Segment
     uint32_t  sizeofcmds; // 指令总大小,即每个segment_command_64的cmdsize的和
     uint32_t  flags; // 标记该文件支持的功能,动态链接器(dyld)的标志MH_NOUNDEFS/MH_DYLDLINK... 
     uint32_t  reserved;   // 仅限64位:保留给未来使用
};
// MachO Header
//__mh_execute_header:
0000000100000000         struct __macho_header64 {
                             0xfeedfacf,                          // mach magic number identifier MH_MAGIC_64
                             0x1000007,                           // cpu specifier
                             0x3,                                 // machine specifier
                             MH_EXECUTE,                          // type of file
                             19,                                  // number of load commands
                             2664,                                // the size of all the load commands
                             MH_NOUNDEFS|MH_DYLDLINK|MH_TWOLEVEL|MH_PIE, // flags
                             0x0                                  // reserved
                         }

二、LoadCommands :指定了文件在虚拟内存中的逻辑结构和布局,相当于简介和目录索引

用ncmds个的segment_command_64存储了各个段的基本信息(第一个是__PAGEZERO segment)
每个segment_command_64后面跟着nsects个的section_64存储了该段内section的基本信息。

//loader.h
struct segment_command_64 { /* for 64-bit architectures */
    uint32_t    cmd;        // 指令类型, 每个指令类型结构都不一样,比如LC_SEMENT_64的具体含义是将文件中的段映射到进程地址空间
    uint32_t    cmdsize;    // 当前Load Command本身的大小,即当前segment_command_64和其跟着的零到多个section_64的大小和。
    char        segname[16];    //该Segment名
    uint64_t    vmaddr;     //该Segment加载到虚拟内存中的地址,从0x000000000开始
    uint64_t    vmsize;     //该Segment在虚拟内存中所占据的空间大小
    uint64_t    fileoff;    //该Segment在MachO文件中的位置
    uint64_t    filesize;   //该Segment在MachO文件中的大小
    vm_prot_t   maxprot;    //表示当前段在虚拟内存中所需要的最高内存保护
    vm_prot_t   initprot;   //表示当前段的初始内存保护
    uint32_t    nsects;     //表示当前段中所包含的Section的数量
    uint32_t    flags;      /* flags */
};

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 */
};
//Load Command 2
0000000100000330         struct __macho_segment_command_64 {
                             LC_SEGMENT_64,                       // LC_SEGMENT_64
                             0x228,                               // includes sizeof section_64 structs
                             "__DATA_CONST", 0, 0, 0, 0,          // segment name
                             0x100004000,                         // memory address of this segment
                             0x4000,                              // memory size of this segment
                             0x4000,                              // file offset of this segment
                             0x4000,                              // amount to map from the file
                             0x3,                                 // maximum VM protection
                             0x3,                                 // initial VM protection
                             0x6,                                 // number of sections in segment
                             SG_READ_ONLY                         // flags
                         }
0000000100000378         struct __macho_section_64 {
                             "__got", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // name of this section
                             "__DATA_CONST", 0, 0, 0, 0,          // segment this section goes in
                             0x100004000,                         // memory address of this section
                             0x18,                                // size in bytes of this section
                             0x4000,                              // file offset of this section
                             0x3,                                 // section alignment (power of 2)
                             0x0,                                 // file offset of relocation entries
                             0x0,                                 // number of relocation entries
                             S_NON_LAZY_SYMBOL_POINTERS,          // flags (section type and attributes
                             0x1b,                                // reserved (for offset or index)
                             0x0,                                 // reserved (for count or sizeof)
                             0x0                                  // reserved
                         }...

__PAGEZERO段
__PAGEZERO是Mach-O加载进内存之后附加的一块区域,它不可读,不可写,主要用来捕捉NULL指针的引用。如果访问__PAGEZERO段,会引起程序崩溃
__PAGEZERO的segment_command_64的filesize为0表示在Mach-O文件中并没有__PAGEZERO段,在Mach-O文件被加载进虚拟内存中,才会附加上__PAGEZERO段。

使用size 指令查看Mach-O内存分布
size -l -m -x Mach-O文件路径

Mach-O内存分布

ASLR
ASLR其实就是Address Space Layout Randomization,地址空间布局随机化。它是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的的一种技术。在iOS 4.3开始引入ASLR技术。
在未使用ASLR技术时,Mach-O被加载进内存后,是从地址0x000000000开始存放,前文说到,Mach-O文件本身是不存在__PAGEZERO的,在Mach-O文件被加载到虚拟内存之后,系统会给Mach-O文件分配一个__PAGEZERO,它的开始位置是0x000000000,结束位置是0x100000000。并且它的大小是固定的。Mach-O本身的内容在虚拟内存中存放的开始位置从0x100000000开始,也就是紧接着__PAGEZERO的结束地址存放__TEXT段和__DATA以及__LINKEDIT段。
在使用了ASLR技术之后,在Mach-O文件加载进内存之后,__PAGEZERO的开始位置就不是从0x000000000开始存放了,ASLR会随机产生一个地址偏移Offset,而__PAGEZERO的开始位置需要在0x000000000的基础上加上偏移量Offset的值,才是真正的存放地址。 假设随机偏移量Offset是0x000005000,那么__PAGEZERO的开始位置就是0x000005000,结束位置就是0x100005000。剩下的__TEXT段、__DATA段和__LINKEDIT段则依次偏移Offset即可。
函数的内存地址(VM Address) = File Offset(函数在MachO中的位置) + ASLR Offset + __PAGEZERO Size
OC中获得总偏移量
#import intptr_t slide_addr = _dyld_get_image_vmaddr_slide(0);

Mach-O文件结构及加载进虚拟内存的映射

三、Raw Segment Data :存放了所有在Load Commands中定义的Segment所对应的原始数据

一、 __TEXT Segment (Readable/Executable):存放了所有函数代码

包含 __cstring, methname, classname, methtype等。

1.Header和Load commands在__TEXT Segment的头部

2. __text Section

可执行文件的代码区,所有方法与函数的实现按照代码定义顺序存储在这里,如果函数内有定义block,则block实现跟在该函数后面,例如-[classB fun1]_block_invoke

  1. 类的成员函数(Method)

    • 自定义的类方法和实例方法
    • 实现的协议类方法和实例方法
    • 编译器生成的getter/setter方法
    • 编译器生成的.cxx_destruct方法(ARC环境)

    .cxx_destruct方法在ARC环境下由编译器生成,实现类销毁时自动释放对象内存的工作。内部实现为对类拥有的所有对象(例如block)调用objc_storeStrong()方法release一次,详细信息见《OC的MRC与ARC内存管理》

  2. 全局纯c函数与纯c类成员函数

  3. main函数

3. __stubs Section

存放代码段调用的每一个外部链接函数的引用, 跳转到__la_symbol_ptr Section懒加载指针表。

4. __stub_helper Section

5. __cstring Section

字符常量区,存类C 风格的字符串

6. __objc_classname Section

  1. 各个类的struct __objc_data {.name}字段(包括分类名)
  2. 如果ivarlayout字符串过长也有可能存在这里,顺便介绍一下ivarlayout

Ivar Layout 计算strong ivar的布局
是一个uint8_t*,以一个字节为一组,前四位表示按Ivarlist顺序排列的ivar表从头计算的非强引用ivar个数,后四位表示接着的强引用ivar个数,循环直到ivarlist结束

7. __objc_methname Section

  1. 各个类的Method的 struct __objc_method {.name}字段
  2. 各个类的Ivar名(非属性和属性自动生成的)struct __objc_ivar {.name}字段
  3. 各个类的属性的attributes struct __objc_property {.attributes}字段

8. __objc_methtype Section(方法签名)

1.各个类的方法的签名 struct __objc_method {.type)字段,不同方法相同type会共用

  • 自定义的类方法和实例方法
  • 编译器生成的getter/setter方法
  • 编译器生成的.cxx_destruct方法(ARC环境)

2.各个类的Ivar类型编码(非属性和属性自动生成的)struct __objc_ivar {.type}

9. __unwind_info Section

二、 __DATA_CONST Segment (Readable/Writable):存放了所有全局变量信息

包含 classlist, protolist, imageinfo, const, selrefs(sel 指针), classrefs, superrefs, ivar, _objc_data(类对象数据结构,包含 isa, super class等), __data(协议类对象数据结构),

1. __got Section

2. __const Section

存block_descriptor

3. __cfstring Section

存放代码段调用的每一个字符串的引用,跳转到__DATA_CONST Segment内的__cstring Section。

4. __objc_classlist Section

自定义的所有类的列表(不包括元类,父类),指向__objc_data Section(类对象数据结构,包含 isa, super class等)里的类定义。

5. __objc_nlclslist Section

自定义的non-lazy class信息,即实现了+load方法的类,,指向__objc_data Section(类对象数据结构,包含 isa, super class等)里的类定义。

6. __objc_protolist Section

自定义的所有协议的列表,指向__data Section(协议类对象数据结构)。

7. __objc_imageinfo Section

三、__DATA Segment (Readable/Writable):存放了所有全局变量信息

包含 classlist, protolist, imageinfo, const, selrefs(sel 指针), classrefs, superrefs, ivar, _objc_data(类对象数据结构,包含 isa, super class等), __data(协议类对象数据结构),

1. __la_symbol_ptr Section

外部链接函数的懒加载指针表,是Section __stubs跳转的位置,指向External Symbols段。

2. __objc_const Section(class_ro_t数据结构及其内容,包含元类,父类等)

  1. 所有类的(协议/非协议)方法的objc_method_list数据结构及其后面的objc_method数据结构
    如果这个类实现了分类,方法结构都会在_objc_data Section里的method_class(cate)定义,同时class_ro_t 的base methods会变为指向这里。
  2. 所有类的objc_ivar_list数据结构及其后面的objc_ivar数据结构
  3. 所有类的objc_property_list数据结构及其后面的objc_property数据结构
  4. 所有类的objc_protocol_list数据结构以及其后面的protocol_t数据结构
  5. 所有类的class_ro_t数据结构
                     __OBJC_CLASS_RO_$_classA:
00000001000084c8         struct __objc_data {                                   ; "classA","\\x11\\x11\\\"\\x11", DATA XREF=_OBJC_CLASS_$_classA
                             0x184,                               // flags
                             8,                                   // instance start
                             88,                                  // instance size
                             0x0,
                             aX11x11x11,                          // ivar layout
                             aClassa_100003d3a,                   // name
                             __OBJC_$_INSTANCE_METHODS_classA,    // base methods
                             __OBJC_CLASS_PROTOCOLS_$_classA,     // base protocols
                             __OBJC_$_INSTANCE_VARIABLES_classA,  // ivars
                             0x0,                                 // weak ivar layout
                             __OBJC_$_PROP_LIST_classA            // base properties
                         }
                     __OBJC_METACLASS_RO_$_classB:
0000000100008510         struct __objc_data {                                   ; "classB", DATA XREF=_OBJC_METACLASS_$_classB
                             0x81,                                // flags
                             40,                                  // instance start
                             40,                                  // instance size
                             0x0,
                             0x0,                                 // ivar layout
                             aClassb,                             // name
                             0x0,                                 // base methods
                             0x0,                                 // base protocols
                             0x0,                                 // ivars
                             0x0,                                 // weak ivar layout
                             0x0                                  // base properties
                         }

3. __objc_selrefs Section

存放代码段引用的selector,跳转到__TEXT Segment内的__objc_methname。

4. __objc_classrefs Section

存放代码段引用的自定义class,跳转到__DATA Segment内的__objc_data Section(类对象数据结构,包含 isa, super class等)里的类定义。

5. __objc_ivar Section

ivar的存储空间,Section __objc_const里struct __objc_ivar {.offset pointer }指向的位置

6. __objc_data Section(类对象数据结构,包含 isa, super class等)

所有类(包括元类和父类)的objc_class数据结构

                     _OBJC_METACLASS_$_classA:
0000000100008718         struct __objc_class {                                  ; DATA XREF=_OBJC_CLASS_$_classA
                             _OBJC_METACLASS_$_NSObject,          // metaclass
                             _OBJC_METACLASS_$_NSObject,          // superclass
                             __objc_empty_cache,                  // cache
                             0x0,                                 // vtable
                             __OBJC_METACLASS_RO_$_classA         // data
                         }

                     _OBJC_CLASS_$_classA:
0000000100008740         struct __objc_class {                                  ; DATA XREF=0x1000042a0, objc_cls_ref_classA
                             _OBJC_METACLASS_$_classA,            // metaclass
                             _OBJC_CLASS_$_NSObject,              // superclass
                             __objc_empty_cache,                  // cache
                             0x0,                                 // vtable
                             __OBJC_CLASS_RO_$_classA             // data
                         }

7. __data Section(协议类对象数据结构)

所有协议的objc_protocol结构

四、External Symbols Segment

一个完整的用户级MachO文件的末端是一系列链接信息。其中包含了动态加载器用来链接可执行文件或者依赖所需使用的符号表、字符串表等
符号决议见:https://www.jianshu.com/p/4493ab03d5b2

__DATA Segment 待补充的 Section:
__nl_symbol_ptr: 非懒加载指针表,dyld 加载会立即绑定
__ls_symbol_ptr: 懒加载指针表
__mod_init_func: constructor 函数
__mod_term_func: destructor 函数
__objc _catlist: Category列表

引用
https://www.qingtingip.com/h_411229.html
https://www.jianshu.com/p/3cf776385759

你可能感兴趣的:(OC底层MachO结构正向与逆向以及虚拟内存加载)