Mach-O文件简介
 
Mach-O是一种文件格式,是Mach Object文件格式的缩写。
它通常应用于可执行文件,目标代码,动态库,内核转储等中。
 
Mach-O作为大部分基于Mach核心的操作系统所使用。
如:NeXTSTEP,Darwin和Mac OS X等系统使用这种格式作为其原生可执行文件,库和目标代码的格式。
 
在NeXTSTEP和Mac OS X中,可以将多个Mach-O文件组合进一个多重架构二进制文件中,以用一个单独的二进制文件支持多种架构的指令集。这种称为胖二进制文件(即:Fat binary文件)。
 
Mach-O文件类型众多,常见的一些Mach-O文件类型如下:
MH_OBJECT    目标文件,平时.o结尾的文件
MH_EXECUTE    可执行文件,我们平时编译后的包中的执行文件
MH_DYLIB    一些动态库,该文件夹下很多/usr/lib/xxx.dylib
MH_DSYM        符号文件,编译成功后XXX.app.dSYM
 

 
Mach-O文件结构布局
Mach-O主要有三部分组成:
Header部分主要描述当前Mach-O文件什么架构,是否Fat二进制文件,CUP类型等等;
Load commands部分主要描述:
1.Mach-O文件中在虚拟内存中空间是如何分配的,从哪个内存地址开始到哪个内存地址结束。
2.不同段在Mach-O文件中的位置,大小分布。
Data部分是描述内存如何被分配的内容。包括__TEXT, __DATA等。

Header结构描述

Fat_Arch的header结构图如下:

如果只有一种架构,那么Fat Header的地方直接就是mac_header

Load command结构描述 

为了方便管理,程序被添加到内存后是分段管理的,而每个段是如何从本地加载到内存是在LC_Type中进行描述的。
Segment的加载命令描述结构如下:
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 */
};
Mach-O文件中不同的内容段__TEXT, __DATA等在虚拟内存中的内存分布是用VM Address和VM Size来描述的;
在Mach-O本地文件中的空间分布是用File Offset和File Size来描述的;
而同一个Segment信息在这两个维度中的空间分配策略,是不完全相同的。
如:
__PAGEZERO段:在arm64架构size时0x100000000,在armv7架构size时0x4000。它会在虚拟内存中隔离出一大块
低地址区的内存空间。而在Mach-O文件中占据的大小为0,并没有实际的内容。当设置一个指针变量的值为NULL时,其实就是将指针指向了__PAGEZERO段这块区域。
__DATA段:在虚拟内存中的分配的空间要大于Mach-O文件中占据的大小。原因是在内存中需要预留一部分多余的空间给可以修改的全局变量或者所占空间可以变化的对象或容器使用。
 
常见的SEG类型
#define SEG_PAGEZERO    "__PAGEZERO"
#define SEG_TEXT "__TEXT" /* the tradition UNIX text segment */
#define SEG_DATA "__DATA" /* the tradition UNIX data segment */
SEG_PAGEZERO:
是一个不可读、不可写、不可执行的段。能够在空指针访问时抛异常。
SEG_TEXT:
代码段,可读、可执行。
SEG_DATA:
数据段,可读、可写。
 
__PAGEZERO段描述

__TEXT段加载描述

 __DATA段加载描述

根据__TEXT段的加载描述, 得到__DATA段内容的偏移地址如下:


 

ASLR:内存空间布局随机化

Mach-O文件利用两种空间描述,来表达自己在Mach-O文件和虚拟内存中不同空间的分配方式
每个app都有自己独立的虚拟内存,这个虚拟内存只存在与自己的Mach-O文件的load commands的描述中。
当app执行时,会被系统映射到实际的物理内存中。
 
程序一旦编译完成,函数就安静的放在了__TEXT段, 全局变量就安静的放在了__DATA段上。等用户点击后,才被加载到内存。
在iOS逆向中,通过Hopper工具反汇编可以看到函数的虚拟内存地址,但是Hopper中展示的内存地址是没有ASLR的。
想要得到函数的真正函数地址,还需要得到当前Mach-O文件在内存中的ASLR偏移值。
 
当app被加载到内时,系统会自动进行ASLR,在__PAGEZERO端的上面随机多出一段空间作为偏移,使得Mach-O文件的整个虚拟内存向下整体(包括堆,栈,共享库映射等线性布局)偏移。从而可以让生成的函数内存地址不断变动。这样可以提高黑客的破解难道。

那如何得到当前Mach-O文件的ASLR偏移值呢?
通过lldb调试器可以查到。
通过lldb调试器的mach-o文件列表查询命令,-o查询所有使用的mach-o文件包括dyld链接编辑器、app的mach-o文件、dylib库。
可以查询到app文件对应Mach-O的所包含的所有Mach-O文件。
//lldb调试器命令,打印app文件对应Mach-O文件的所包含的所有Mach-O文件。
image list -o -f

结果如下:

(lldb) image list -o -f
[ 0] 0x0000000000558000 /Users/zhoufei/Library/Developer/Xcode/DerivedData/SorterAndFilter-gcqrjckyrquurscwtbwpiaeebgzj/Build/Products/Release-iphoneos/SorterAndFilter.app/SorterAndFilter
[ 1] 0x0000000100800000 /Users/zhoufei/Library/Developer/Xcode/iOS DeviceSupport/11.4 (15F79)/Symbols/usr/lib/dyld
[ 2] 0x0000000001190000 /Users/zhoufei/Library/Developer/Xcode/iOS DeviceSupport/11.4 (15F79)/Symbols/System/Library/Frameworks/Foundation.framework/Foundation
[ 3] 0x0000000001190000 /Users/zhoufei/Library/Developer/Xcode/iOS DeviceSupport/11.4 (15F79)/Symbols/System/Library/Frameworks/UIKit.framework/UIKit
[ 4] 0x0000000001190000 /Users/zhoufei/Library/Developer/Xcode/iOS DeviceSupport/11.4 (15F79)/Symbols/usr/lib/libobjc.A.dylib
[ 5] 0x0000000001190000 /Users/zhoufei/Library/Developer/Xcode/iOS DeviceSupport/11.4 (15F79)/Symbols/usr/lib/libSystem.B.dylib
[ 6] 0x0000000001190000 /Users/zhoufei/Library/Developer/Xcode/iOS DeviceSupport/11.4 (15F79)/Symbols/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
[ 7] 0x0000000001190000 /Users/zhoufei/Library/Developer/Xcode/iOS DeviceSupport/11.4 (15F79)/Symbols/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics
[ 8] 0x0000000001190000 /Users/zhoufei/Library/Developer/Xcode/iOS DeviceSupport/11.4 (15F79)/Symbols/System/Library/Frameworks/QuartzCore.framework/QuartzCore
[ 9] 0x0000000001190000 /Users/zhoufei/Library/Developer/Xcode/iOS DeviceSupport/11.4 (15F79)/Symbols/usr/lib/libarchive.2.dylib

第0个结果0x0000000000558000 就是要找的ASLR值

 
如何证明全部变量在Mach-O中,局部变量在栈中,对象在堆空间中?
根据打印的内存地址可以进行说明

通过log的内容,拿到app的Mach-O文件路径:

[  0] 0x0000000000558000 /Users/zhoufei/Library/Developer/Xcode/DerivedData/SorterAndFilter-gcqrjckyrquurscwtbwpiaeebgzj/Build/Products/Release-iphoneos/SorterAndFilter.app/SorterAndFilter

其对应的偏移地址0x0000000000558000 就是Mach-O文件从本地添加到内存时,系统自动添加的ASLR:内存空间布局随机化值。

那Mach-O文件SorterAndFilter.app/SorterAndFilter的大小是多少呢?
通过终端,进行查询
1.cd /Users/zhoufei/Library/Developer/Xcode/DerivedData/SorterAndFilter-gcqrjckyrquurscwtbwpiaeebgzj/Build/Products/Release-iphoneos/SorterAndFilter.app/

2.size -l -m -x SorterAndFilter

的到结果如下:

SorterAndFilter (for architecture arm64):
Segment __PAGEZERO: 0x100000000 (vmaddr 0x0 fileoff 0)
Segment __TEXT: 0x1c000 (vmaddr 0x100000000 fileoff 0)
Section __text: 0xfde0 (addr 0x100005740 offset 22336)
Section __stubs: 0x1bc (addr 0x100015520 offset 87328)
Section __stub_helper: 0x1d4 (addr 0x1000156dc offset 87772)
Section __const: 0x64 (addr 0x1000158b0 offset 88240)
Section __objc_methname: 0x36b4 (addr 0x100015914 offset 88340)
Section __ustring: 0x134 (addr 0x100018fc8 offset 102344)
Section __cstring: 0xd06 (addr 0x1000190fc offset 102652)
Section __objc_classname: 0x28c (addr 0x100019e02 offset 105986)
Section __objc_methtype: 0x19f2 (addr 0x10001a08e offset 106638)
Section __gcc_except_tab: 0xd8 (addr 0x10001ba80 offset 113280)
Section __unwind_info: 0x4a4 (addr 0x10001bb58 offset 113496)
total 0x168bc
Segment __DATA: 0xc000 (vmaddr 0x10001c000 fileoff 114688)
Section __got: 0x60 (addr 0x10001c000 offset 114688)
Section __la_symbol_ptr: 0x128 (addr 0x10001c060 offset 114784)
Section __const: 0x9f0 (addr 0x10001c188 offset 115080)
Section __cfstring: 0x9a0 (addr 0x10001cb78 offset 117624)
Section __objc_classlist: 0x90 (addr 0x10001d518 offset 120088)
Section __objc_catlist: 0x28 (addr 0x10001d5a8 offset 120232)
Section __objc_protolist: 0x58 (addr 0x10001d5d0 offset 120272)
Section __objc_imageinfo: 0x8 (addr 0x10001d628 offset 120360)
Section __objc_const: 0x55d8 (addr 0x10001d630 offset 120368)
Section __objc_selrefs: 0x9c0 (addr 0x100022c08 offset 142344)
Section __objc_classrefs: 0x138 (addr 0x1000235c8 offset 144840)
Section __objc_superrefs: 0x68 (addr 0x100023700 offset 145152)
Section __objc_ivar: 0xd0 (addr 0x100023768 offset 145256)
Section __objc_data: 0x5a0 (addr 0x100023838 offset 145464)
Section __data: 0x430 (addr 0x100023dd8 offset 146904)
Section __bss: 0x48 (addr 0x100024208 offset 0)
total 0x8250
Segment __LINKEDIT: 0x24000 (vmaddr 0x100028000 fileoff 163840)
total 0x10004c000

在虚拟内存中,Mach-O文件SorterAndFilter的总大小是total 0x10004c000。

 
由于Mach-O文件在虚拟内存中的__PAGEZERO段的大小是0x100000000
所以在本地文件中Mach-O文件的大小是:0x10004c000 - 0x100000000 等于0x4c000
 
因为系统自动添加的ASLR值是0x558000,所以在虚拟内存中,Mach-O文件SorterAndFilter的空间分布是:
0x558000 -> 0x1005A4000 (0x10004c000 + 0x558000 )
 
根据上面内存地址信息log的值:
2020-01-12 16:47:32.388332+0800 SorterAndFilter[1717:366886] 全局变量:0x10057bf58, 局部变量:0x16f8a57fc, 局部变量—对象指针:0x16f8a57f0, 堆空间-对象地址:0x100aace30

动态链接器dyld的虚拟内存地址

[  1] 0x0000000100800000 /Users/zhoufei/Library/Developer/Xcode/iOS DeviceSupport/11.4 (15F79)/Symbols/usr/lib/dyld

Mach-O文件SorterAndFilter所有使用的到的image(image都是Mach-O类型文件)文件内存分布,得到虚拟内存中的内存分布如下:

这里有个很奇怪的地方,系统共享库的虚拟内存地址0x1190000,是在Mach-O文件SorterAndFilter的虚拟内存空间范围内
我感觉比较合理的解释是打印出来的0x1190000是app中stub代码区地址。
 
那何为stub代码区呢?
Stub代码区是本地源代码调用系统库代码的连接点,当app工程中有调用系统函数的代码时,在app编译后,那个调用系统函数的处的内存地址便指向了stub代码区 。
app从本地加载到内存时,使用的系统函数API在app的可执行文件文件中并没有函数实现,需要dyld动态链接器将系统API与系统函数地址进行绑定。
而根据绑定时机不同,绑定分为app加载时binding和函数调用时lazy_binding。
下面以lazy_binding绑定方式为例:
1.点击按钮,触发本地函数对系统函数的调用,此时被调用的系统函数指针是指向的stub代码区,
2. 在stub代码区的实现中,又将函数指针指向了懒动态符号表。
3.懒动态符号表又将函数指针指向了stub_helper代码区。
4.最后stub_helper代码区通过dyld_stub_binder函数将真实的系统函数内存地址更新到懒动态符号表中。
 
具体动态绑定流程图如下:
_nl_symbol_ptr列表在加载时绑定。
_la_symbol_ptr列表在第一次使用时进行函数绑定。
_la_symbol_ptr懒动态符号列表的绑定过程如下:


 
Mach-O类型文件工具
Mach-O类型文件工具有很多,常见的如下:
 
系统自带工具
file: 查看Mach-O的文件类型
nm:查看Mach-O文件的符号表
otool:查看Mach-O特定部分和段的内容
size -l -m -x: 查看Mach-O不同段的虚拟内存分布
lipo:常用用于多架构Mach-O文件的处理
查看通用二进制文件包含的架构
lipo -info test
瘦身通用二进制文件,到包含指定架构(armv7)的瘦二进制文件
lipo test -thin armv7 -output test_armv7
合并两个瘦二进制文件到一个通用二进制文件
lipo -create test_armv7 test_arm64 -output test2
 
Xcode自带工具
lldb调试器命令,打印所有app文件对应Mach-O的所包含的所有Mach-O文件。
image list -o -f
 
第三方工具
class-dump:可以把未经加密的app的头文件导出来。
//产生头文件 
class-dump -H test -o Headers

MachOView: GUI工具查看Mach-O文件

HopperDisassembler: 反汇编查看Mach-O文件中的函数和全局变量的信息。
 
有了工具的使用,在分析Mach-O文件时会变的轻松很多。
 
 
 
 
 

关于Mach-O类型文件那点事的更多相关文章

  1. 在文件夹中 的指定类型文件中 查找字符串(CodeBlocks+GCC编译,控制台程序,仅能在Windows上运行)

    说明: 程序使用 io.h 中的 _findfirst 和 _findnext 函数遍历文件夹,故而程序只能在 Windows 下使用. 程序遍历当前文件夹,对其中的文件夹执行递归遍历.同时检查遍历到 ...

  2. C#项目打开/保存文件夹/指定类型文件,获取路径

    C#项目打开/保存文件夹/指定类型文件,获取路径 转:http://q1q2q363.xiaoxiang.blog.163.com/blog/static/1106963682011722424325 ...

  3. asp.net word ecxel类型文件在线预览

    asp.net word ecxel类型文件在线预览 首先得引用COM: Microsoft Excel 10 Object Library Microsoft Word 10 Object Libr ...

  4. Linux下find一次查找多个指定类型文件,指定文件或者排除某类文件,在 GREP 中匹配多个关键 批量修改文件名等

    http://blog.sina.com.cn/s/blog_62e7fe670101dg9d.html linux下二进制文件查找: strings 0000.ts | grep -o " ...

  5. CSharp tar类型文件压缩与解压

    最近闲暇时间开始写点通用基础类在写到tar类型文件压缩与解压时遇到点问题 压缩用的类库我是下载的 SharpZipLib_0860版本 先上代码 加压核心 /// <summary> // ...

  6. java_eclipse_svn 与服务器同步时 ,忽略某类型文件和文件夹

    1. 在项目开发中使用svn ,带来很大的方便,有时我们会把整个项目上传的svn服务器上 这样就包含了  编译过的class文件  以及 一些 .svn,.log文件,有些文件时本地complie 的 ...

  7. linux下删除目录及其子目录下某种类型文件

    Linux下,如果想要删除目录及其子目录下某种类型文件,比如说所有的txt文件,则可以使用下面的命令: find . -name "*.txt" -type f -print -e ...

  8. 各种类型文件的Content-Type

    各种类型文件的Content-Type 2017年11月27日 10:00:56 thebigdipperbdx 阅读数:7360   版权声明:本文为博主原创文章,未经博主允许不得转载. https ...

  9. 游戏开发中IIS常见支持MIME类型文件解析

    游戏开发中IIS常见支持MIME类型文件解析 .apkapplication/vnd.android .ipaapplication/vnd.iphone .csbapplication/octet- ...

随机推荐

  1. Android 隐藏EditText的焦点

    在页面的开发过程中,我们可能会遇到这样的情况,打开某个页面(Activity)时,如果该页面中有EditText组建,则会自动弹出软键盘(因为该EditText自动获取焦点了),这样很容易影响用户体验 ...

  2. Jieba分词包(一)——解析主函数cut

    1. 解析主函数cut Jieba分词包的主函数在jieba文件夹下的__init__.py中,在这个py文件中有个cut的函数,这个就是控制着整个jieba分词包的主函数.    cut函数的定义如 ...

  3. HDU1686 Oulipo 题解 KMP算法

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1686 题目大意:给你一个子串t和一个母串s,求s中有多少个子串t. 题目分析:KMP模板题. cal_ ...

  4. JS划重点——类和对象的不正经阐述

    JS划重点--类和对象的不正经阐述 /在JS 类里面函数也是一个对象,那么要创建一个对象就需要一个类,这个类可以由这个对牛逼的对象-函数来实现/ /首先是普罗大众都会的 工厂模式来创建一类/ func ...

  5. Python图表绘制Matplotlib

    引入 import numpy as npimport pandas as pdimport matplotlib.pyplot as plt# 导入相关模块 使用 # 图表窗口1 → plt.sho ...

  6. 1、Python 日期时间格式化输出

    今天帮朋友写自动化脚本,又需要用格式化日期,又忘记怎么写了,还是写到自己博客里面,方便日后需要的时候看一眼吧.So,临时加一篇 Python 的文章. 1.Python的time模块 import t ...

  7. 报错:org.springframework.beans.factory.BeanCreationException

    报错码为以下内容,把自己走的坑贴出来,免得大家如同样的坑.以下解决方法仅供参考. ERROR [RMI TCP Connection(3)-127.0.0.1] - Context initializ ...

  8. java 反射和泛型-反射来获取泛型信息

    通过指定对应的Class对象,程序可以获得该类里面所有的Field,不管该Field使用private 方法public.获得Field对象后都可以使用getType()来获取其类型. Class&l ...

  9. P1047 汉诺塔

    题目描述 汉诺塔是根据一个印度传说形成的数学问题:有三根杆子A, B, C, A杆上有n个穿孔圆盘, 盘的尺寸由下到上依次变小. 要求按照下列规则将所有圆盘移至C杆: 每次只能移动一个圆盘 大盘不能叠 ...

  10. C# 转换类型和字符串

    有时候我们需要互转类型和字符串,把字符串转类型.把类型转字符串. 如果是基础类型,可以使用 x.Parse 这个方法,很多基础类型都支持. 那么我们可以使用 TypeDescriptor string ...