iOS 系统相关复习

沙盒

iOS沙盒详细介绍
iOS沙盒篇

沙盒机制介绍

iOS中的沙盒机制是一种安全体系。为了保证系统安全,iOS每个应用程序在安装时,会创建属于自己的沙盒文件(存储空间)。应用程序只能访问自身的沙盒文件,不能访问其他应用程序的沙盒文件,当应用程序需要向外部请求或接收数据时,都需要经过权限认证,否则,无法获取到数据。所有的非代码文件都要保存在此,例如属性文件plist、文本文件、图像、图标、媒体资源等,其原理是通过重定向技术,把程序生成和修改的文件定向到自身文件夹中。

注意:
APP之间不能相互通,唯独可以通过URL Scheme可以通信。
每次编译代码会生成新的沙盒路径,注意是编译不是启动,所以模拟器或者真机运行你每次运行所得到的沙盒路径都是不一样,线上版本app真机不会生成新的沙盒路径

沙盒的目录结构

每个APP的沙盒下面都有相似目录结构,如下图


iOS 系统相关复习_第1张图片
image.png

沙盒中相关路径

AppName.app 应用程序的程序包目录,包含应用程序的本身。由于应用程序必须经过签名,所以不能在运行时对这个目录中的内容进行修改,否则会导致应用程序无法启动。

Documents/ 保存应用程序的重要数据文件和用户数据文件等。用户数据基本上都放在这个位置(例如从网上下载的图片或音乐文件),该文件夹在应用程序更新时会自动备份,在连接iTunes时也可以自动同步备份其中的数据。

Library:这个目录下有两个子目录,可创建子文件夹。可以用来放置您希望被备份但不希望被用户看到的数据。该路径下的文件夹,除Caches以外,都会被iTunes备份.

Library/Caches: 保存应用程序使用时产生的支持文件和缓存文件(保存应用程序再次启动过程中需要的信息),还有日志文件最好也放在这个目录。iTunes 同步时不会备份该目录并且可能被其他工具清理掉其中的数据。
Library/Preferences: 保存应用程序的偏好设置文件。NSUserDefaults类创建的数据和plist文件都放在这里。会被iTunes备份。

tmp/: 保存应用运行时所需要的临时数据。不会被iTunes备份。iPhone重启时,会被清空。

Documents/Inbox:该目录用来保存由外部应用请求当前应用程序打开的文件。比如:应用A向系统注册了几种可打开的文件格式,应用B中有一个A支持的格式的文件F,并申请调用应用A打开文件F。由于F当前是在应用B的沙盒中,我们知道,沙盒机制使不允许应用A访问应用B沙盒中的文件,因此苹果的解决方案是将文件F拷贝一份到应用A的Documents/Inbox目录下,再让应用A打开文件F。会被iTunes备份。

访问沙盒目录常用C函数介绍

//文件路径搜索
FOUNDATION_EXPORT NSArray *NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory directory, NSSearchPathDomainMask domainMask, BOOL expandTilde);

该方法返回值为一个数组,在iphone中由于只有一个唯一路径,所以直接取数组第一个元素即可.

参数1:指定搜索的目录名称,比如这里用NSDocumentDirectory表明我们要搜索的是Documents目录。如果我们将其换成NSCachesDirectory就表示我们搜索的是Library/Caches目录。

参数2:搜索主目录的位置,NSUserDomainMask表示搜索的范围限制于当前应用的沙盒目录。还可以写成NSLocalDomainMask(表示/Library)、NSNetworkDomainMask(表示/Network)等

参数3:是否获取完整的路径,我们知道在iOS中的全写形式是/User/userName,该值为YES即表示写成全写形式,为NO就表示直接写成“~”。
该值为NO:Caches目录路径为~/Library/Caches
该值为YES:Caches目录路径为
/var/mobile/Containers/Data/Application/E7B438D4-0AB3-49D0-9C2C-B84AF67C752B/Library/Caches

typedef NS_OPTIONS(NSUInteger, NSSearchPathDomainMask) {
    NSUserDomainMask = 1,       // 用户目录 - 基本上就用这个。 
    NSLocalDomainMask = 2,      // 本地
    NSNetworkDomainMask = 4,    // 网络 
    NSSystemDomainMask = 8,     // 系统
    NSAllDomainsMask = 0x0ffff  // 所有 
};

//常用的NSSearchPathDirectory枚举值
typedef NS_ENUM(NSUInteger, NSSearchPathDirectory) {
    NSApplicationDirectory = 1,             // supported applications (Applications)
    NSDemoApplicationDirectory,             // unsupported applications, demonstration versions (Demos)
    NSAdminApplicationDirectory,            // system and network administration applications (Administration)
    NSLibraryDirectory,                     // various documentation, support, and configuration files, resources (Library)
    NSUserDirectory,                        // user home directories (Users)
    NSDocumentationDirectory,               //  Library 下的(Documentation)模拟器上没有创建
    NSDocumentDirectory,                    // documents (Documents)

};

获取沙盒路径

  1. 获取沙盒主目录路径
NSString *homePath = NSHomeDirectory();
  1. 获取Documents路径
NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
  1. 获取Library路径
NSString *libraryPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
  1. 获取Caches路径
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
  1. 获取tmp路径
NSString *tmpDir = NSTemporaryDirectory();

文件读写

向沙盒中写入文件

NSString *documentsPathW = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES)[0];
//写入文件
if (!documentsPathW) {
    NSLog(@"目录未找到");
} else {
    NSString *filePaht = [documentsPath stringByAppendingPathComponent:@"test.txt"];
    NSArray *array = [NSArray arrayWithObjects:@"code",@"change", @"world", @"OK", @"", @"是的", nil];
    [array writeToFile:filePaht atomically:YES];
}

向沙盒中读取文件

NSString *documentsPathR = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES)[0];
NSString *readPath = [documentsPathR stringByAppendingPathComponent:@"test.txt"];
NSArray *fileContent = [[NSArray alloc] initWithContentsOfFile:readPath];
NSLog(@"文件内容:%@",fileContent);

Bundle(AppName.app的内容)

iOS基础之Bundle详解
iOS:NSBundle的一些理解

Bundle的使用

一般我们从bundle中获取一张图片,可以有这样的获取思路:

1)获取主bundle
2)获取自定义bundle
3)获取自定义bundle中的资源
通常可以这样写:

//主bundle,也就是可执行的工程的bundle
NSBundle *mainBundle = [NSBundle mainBundle];
//NSBundle *mainBundle = [NSBundle bundleForClass:[self class]];
//放在主工程中的自定义bundle
NSString *myBundlePath = [mainBundle pathForResource:@"MyBundle" ofType:@"bundle"];
NSBundle *myBundle = [NSBundle bundleWithPath:myBundlePath];
//放在自定义bundle中的图片
NSString *imagePath = [myBundle pathForResource:@"123" ofType:@"png"];
self.image = [UIImage imageWithContentsOfFile:imagePath];
Bundle的定义和特点

Bundle是一个含有可执行的代码及代码所需资源,以特定标准的层次结构组合起来的文件夹。这里的可执行是指编译过后可直接运行的代码程序,一个典型的例子就是iOS程序打包后的ipa,我们解压ipa后会得到一个payload文件夹,进入文件夹之后会发现一个和ipa名称相同的.app文件夹。这个.app文件夹就是一个Bundle。

系统如何识别Bundle呢?一般而言一个文件夹如果带着.app,.bundle,.framework,.plugin,.kext等等特定后缀,那么系统就认为是Bundle。如果使用Xcode创建项目的话,Xcode会提供相应的模板来生成正确的Bundle类型。
从上面的后缀我们也可以看出Bundle主要分为:

  • Application。应用程序,包含代码和资源。iOS和macOS的app就是这种。
  • Frameworks。框架,包含动态共享库和相应资源。我们常用的系统库和第三方库都属于这种。
  • Plug-Ins。插件,macOS很多的系统功能支持插件,一种动态加载代码模块的方式。

使用Bundle可以很方便的管理程序的文件内容,进行本地化设置、程序移动和运行等等。

Bundle的内部结构

Application Bundle的结构
Application类型的bundle是很常见的,一般里面会包含一下几中类型的文件:

  • Info.plist文件。每个程序中必须有这个文件,因为它包含了程序运行的配置信息,是系统运行程序的依据。
  • 可执行代码文件。这是程序的主体,包含了程序的进入点和链接的静态代码。
  • 资源文件。程序运行过程中需要的资源,比如图片,音频,视频,多语言的字符串文件,nib文件,数据文件,配置文件等等。这里面的大部分文件都可以根据语言、地区、设备通过特定的结构或命名方式加以区分,程序会自动识别加载。
  • 其他支持文件。Mac app可以嵌入高层资源,比如私有库,插件,文件末班,自定义数据资源等等。iOS app可以包含自定义数据资源,但是不能包含私有库和插件。

如果我们对于ipa中的.app文件夹右键显示包内容,我们可以看到app bundle的整体结构如下:

MyApp  
  MyApp  //代码编译链接后的可执行文件  
  Info.plist //程序信息
  Assets.car   //Assets中的资源对应的文件。    
  MyAppIcon.png  //所有的icon图片   
  LaunchImage.png  //所有启动图片 
  nibs  //工程中的xib,storyboard生成的nib文件 
  customResource  //工程目录下或者更里层文件夹下的图片,音、视频,数据文件等自定义资源  
  customBundle.bundle  //工程中使用的其他第三方或资源的bundle  
  en.lproj  //语言区分的文件夹,可以使字符串、图片,nib文件等
    MyString  
    MyImage
  zh-Hans.lproj  
    MyString  
    MyImage  
  PkgInfo  //系统识别信息  
  _CodeSignature  //签名信息文件夹,里面的CodeResources包含对bundle中的所有资源文件的签名信息  
  embedded.mobileprovision //打包的配置文件信息  

Assets.car无法直接打开,可以使用工具 cartool 列出包含的所有文件名称。PkgInfo、签名信息和嵌入配置文件都是打包后生成的,是系统验证app的依据。

Framework Bundle结构

Framework Bundle和app bundle的最大不同在于框架库有版本控制,因此必须包含版本列表和当前版本信息。
Framework的bundle结构如下:

MyFramework.framework/
    MyFramework -> Versions/Current/MyFramework  
    Resources   -> Versions/Current/Resources  
    Versions/
        A/
            MyFramework
            Headers/
                MyHeader.h
            Resources/ 
                Eglish.lproj/
                    InfoPlist.strings 
                Info.plist
        Current -> A

上面的根目录下的MyFramework和Resources,以及Versions下面的current 都是文件引用,表明引用文件的位置。真正的代码,资源,header等内容都放在Versions文件夹下面的具体版本文件夹中,Current就指向当前版本的引用。

Bundle的基本使用

使用bundle主要都是围绕着定位资源路径而来的。包括:获取bundle及其信息(不管是main bundle还是其他bundle),获取资源路径(根据资源名称和类型定位),使用系统API直接获取资源(比如图片、音频、本地化字符串、context help,nibs,Info.plist内容等等)。

在磁盘上查找资源时,Bundle对象遵循特定的搜索模式。首先返回全局资源(即不在特定于语言的.lproj目录中的资源),然后返回特定于区域和语言的资源。此搜索模式表示捆绑包按以下顺序查找资源:

  • 全局(非本地化)资源
  • 特定于区域的本地化资源(基于用户的区域首选项)
  • 特定于语言的本地化资源(基于用户的语言首选项)
  • 开发语言资源(由bundle的Info.plist文件中的CFBundleDevelopmentRegion键指定)

由于全局资源优先于特定于语言的资源,因此您不应在应用程序中同时包含给定资源的全局和本地化版本。存在资源的全局版本时,永远不会返回特定于语言的版本。这种优先权的原因是性能。如果首先搜索本地化资源,则bundle对象可能会浪费时间在返回全局资源之前搜索不存在的本地化资源。

在查找资源文件时,bundle对象在确定要返回的文件时会自动考虑许多标准文件名修饰符。可以为特定设备(iphone,ipad)或特定屏幕分辨率(@2x,@ 3x)标记资源。指定所需资源的名称时,请不要包含这些修饰符。 bundle对象选择最适合底层设备的文件

NSBundle的基本使用

NSBundle这个类其实就是用来定位可执行资源的。获取到具体的可执行文件的位置,然后再加载。

mainBundle和bundleForClass都是返回一个NSBundle对象。

mainBundle
对于所有Mach-O Type类型,mainBundle返回的都是可执行工程的bundle。
例如:有一个Executable工程Demo,使用到了动态库工程DynamicFramework和静态库的工程StaticFramework,那么无论是在Demo中,还是DynamicFramework和StaticFramework中,最终mainBundle返回的都是Demo的bundle!

bundleForClass
如果是对于Executable类型的工程,或者是静态的工程,无论class是属于可执行Executable类型的工程,还是属于其他的静态库,最终返回的是main bundle,相当于我们上面的[NSBundle mainBundle]的返回结果。相反的,对于动态的工程,可以获取到该工程的bundle。

imageNamed与imageWithContentsOfFile

imageNamed与imageWithContentsOfFile小结
[UIImage imageNamed:]方法导致的卡顿优化
关于 iOS Asset 探讨

结论

图像的重复利用使用imageNamed的。例如:需要在一个TableView里重复加载同样一个图标,那么用imageNamed加载图像,系统会把那个图标Cache到内存,在Table里每次利用那个图像的时候,只会把图片指针指向同一块内存。这种情况使用imageNamed加载图像就会变得非常有效。

不需要重用该图像,或者要通过网络下载或加载大的图像时,使用imageWithContentsOfFile的方式加载图像

从磁盘加载图片,UIImage主要提供了两种方式:

+(UIImage *)imageNamed:(NSString *)name;
+(UIImage *)imageWithContentsOfFile:(NSString *)path;

imageNamed:第一次加载图片时会缓存图片到内存,下次直接从缓存中读取,适合使用频繁的图片。 //这种初始化方法可以直接不给出图片的具体名字,它会自动匹配
优点:
方便快捷,只有第一次使用的时候稍慢,接下来在使用就会快些.
缺点:
如果在当前工程中大量会大量的浪费内存空间
imageNamed:方法还有个限制,它是在main bundle里找图片,如果图片放在Images.xcassets或者直接把图片方在工程里,参数直接传图片名可以找到。像我司的图片是放在单独建立的bundle里,如果要用imageNamed:加载的话文件名前面就要加上bundle名,像这样a.bundle/b.png。

imageWithContentsOfFile:根据路径去读取,不会把图片缓存到内存,每次调用都要重新从磁盘加载一次。如果图片在当前工程中只使用一次,应该选择这个方法。

屏幕适配问题
iOS的图片文件需要提供3种尺寸的1x、2x、3x,根据不同的屏幕尺寸我们需要加载不同的图片,关于不同屏幕的图片加载,苹果已经帮我们封装好了,我们只需要将3中尺寸的图片放到工程中,然后调用imageNamed:或者imageWithContentsOfFile:,它会自动根据屏幕尺寸来加载不同的图片。
关于imageNamed:,官方文档中有这么一段讨论:

This method looks in the system caches for an image object with the specified name and returns the variant of that image that is best suited for the main screen.

imageWithContentsOfFile:还没找到官方文档的说明(如果各位知道,欢迎各位大牛在评论中提出),不过我测试过是可以的。

 #pragma mark --- 将NSData类型的数据存储到本地(以图片为例)
 //这种必须拼接图片的全名称,否则image路径为空
  NSString *filePath = [[NSBundle mainBundle] pathForResource:@"[email protected]" ofType:nil];
  UIImage *image = [[UIImage alloc] initWithContentsOfFile:filePath];    

  //将image类型的对象转换成为NSData类型数据进行存储
  //使用UIImageJPEGRepresentation:将图片转换成为NSData类型   
  //第一个参数:要转换的image对象         
  //第二个参数:表示图片压缩的值   
  //IPhone中将大于2M的图片,使用该方法,最终会将图片保存成jpeg格式              
   NSData *imageData = UIImageJPEGRepresentation(image, 1); 
  //找到路径存储
   NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];  
  //最终路径 
   NSString *imagePath = [documentPath stringByAppendingString:@"/1234.jpeg"];
   [imageData writeToFile:imagePath atomically:YES]; 
   NSLog(@"imagePath = %@",imagePath);

  //读取NSData类型的数据
  //需求:将NSData类型的数据读取出来,转换成为UIImage类型并且显示在imageView上
   NSData *newData = [NSData dataWithContentsOfFile:imagePath];
   UIImage *showImage = [[UIImage alloc] initWithData:newData];
   UIImageView *showImageView = [[UIImageView alloc] initWithImage:showImage];
   [self.view addSubview:showImageView];

应用间的跳转

iOS - URL Schema
Universal Link以及支持微信和QQSDK
iOS Universal Links(通用链接)

Schema

schema是iOS9之前比较主流的一种跳转方案了, 更多的是用在了两个APP相互跳转中。也可以在Safari中输入schema://跳转到App内部。在每次跳转的时候都会弹框询问。如果iPhone中如果没有安装则会直接弹出错误提示

注册Schema

在Info.plist中进行配置
URL Identifier,一个字符串对象。该字符串是你自定义的 URL scheme 的名字。建议采用反转域名的方法保证该名字的唯一性,比如 com.yourCompany.yourApp。

iOS 系统相关复习_第2张图片
image

通过自定义 URL Scheme 向应用传递参数

image

第一步:在模拟器safari中输入:

LionsomApp://?token=123abc®istered=1

第二步:跳转到应用中获取参数:

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options {
    NSLog(@"从哪个app跳转而来 Bundle ID: %@", options[UIApplicationOpenURLOptionsSourceApplicationKey]);    
    NSLog(@"URL scheme:%@", [url scheme]);
    NSLog(@"URL query:%@", [url query]);
    // 允许打开
    return YES;
}
image

Universal Link

Univeresal Link(通用链接) 是 iOS9推出的特性,当用户点击通用链接时,iOS设备可以不通过Safari或网页,直接打开App,比如在备忘录中直接打开App;
同时由于通用链接是标准的HTTPS链接,既可以打开App,也可以打开网页(在未安装App的时候)。
同时可以使用通用链接,在不同App页面跳转,以及传递参数。

Universal Links(通用链接)的优点

唯一性:不像自定义的schema链接,通用链接不会被其它的APP所使用。因为它使用标准的https链接到你自己的域名。【因为你的域名不会被其他人所使用】
安全性:当用户下载APP的时候,iOS会检查你上传到web服务器的文件以确保您的网站允许您的应用程序以其名义打开网址。因为只有本人有权利创建且上传该文件到服务器,所以网站和APP的关联是安全的。
灵活性:甚至在iOS设备没有安装你项目的时候通用链接也会正常工作。当设备没有安装APP的时候,点击通用链接会在Safari展示你网站的内容。
灵活性:一个通用链接可以同时作用于项目和网站中,其它APP可以在不知道你的APP是否安装的情况下与你的APP通信

如何支持Universal Link

1 . 设置配置文件appapple-app-site-association, 注意:该文件没有后缀, 格式为
{
    "applinks": {
        "apps": [],
        "details": [
            {
                "appID": "D3KQX62K1A.com.example.appstore",
                "paths": [
                    "/test/*","/qq_conn/10000000/*"
                ]
            },
            {
                "appID": "X62K1AD3KQ.com.example.enterprise",
                "paths": [
                    "/test/*","/qq_conn/10000000/*"
                ]
            }
        ]
    }
}

复制代码

其中, AppID的格式为 . Paths中第一个为你自己的路径, 可以只填通配符 *, 第二个为QQ的通用链接配置,10000000是你的QQ AppId

然后将该文件上传到你的网站的 .well-known文件夹或者根目录下。

例如:https://yourdomain/.well-known/apple-app-site-association
或者 https://yourdomain/apple-app-site-association

具体可参考官网 Settiing up an App's Assocaiated Domains

配置 Assocaiated Domains

2.1 在developer.apple.com中打开Assocaiated Domains

iOS 系统相关复习_第3张图片
image.png

2.2 在Xcode -> TARGETS -> Signing & Capabilities中添加Associated Domains功能,添加配置如下

iOS 系统相关复习_第4张图片
image.png

2.3. 上述两项配置完成后,重装真机后,在Safari中输入 app.yourDoamin.com/test/, 打开后下拉可以看到如下页面,即表示通用链接接通

image.png

进入app后的处理

现在用户点击某个链接,直接可以进我们的app了,但是我们的目的是要能够获取到用户进来的链接,根据链接来展示给用户相应的内容。
我们需要在工程里的 AppDelegate 里实现方法

- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler
{
    if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb])
    {
        NSURL *url = userActivity.webpageURL;
        if (url是我们希望处理的)
        {
            //进行我们的处理
        }
        else
        {
            [[UIApplication sharedApplication] openURL:url];
        }
    }
    
    return YES;
}

ASLR (地址空间布局随机化)

iOS逆向工程(九):ASLR
Mach-O文件介绍之ASLR(进程地址空间布局随机化)

概述

ASLR(Address space layout randomization)是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的。

ASLR前言

以前我们用Xcode的LLDB指令打断点时,可以用方法名打断点,例如breakpoint set -n "[UIViewController touchBegin:]",但是我们想动态调试别人的App,就不能用方法名称了,需要用到方法的内存地址才能打,例如 breakpoint set --address 0x123123123
而想知道方法的内存地址就需要学习ASLR

一、什么是ASLR?
  • ASLR,全称是Address Spce Layout Randomization,翻译过来就是地址空间布局随机化,是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,增加了攻击者预测目的地址的难度,防止攻击者直接定位代码位置,阻止溢出攻击。
  • 苹果从iOS 4.3之后引入了ASLR技术,所以想要逆向别人的App,学会ASLR是必不可少的。
  • ASLR的概念看着复杂,实际上非常简单,其实就是当Mach-O文件载入虚拟内存的时候,起始地址不从0x00000000开始了,而是随机增加一段,例如从0x50000开始,后面的函数地址都会增加0x50000
二、Mach-O文件的内部结构
    1. Mach-O文件的内部分为三个区域:HeaderLoad CommandsData,如下图所示:

      image
    1. Header区,存放了CPU类型、Mach-O文件的具体类型、LoadCommands的数量、LoadCommands的大小等数据,如下所示:

      image
    1. Load Commands区,存放了各段的名称、各段的虚拟内存地址、各段的虚拟内存大小、各段的在Mac-O中的偏移量、各段的在Mac-O中的偏移大小等数据 ,如下图所示,重点注意看VM Address、VM SizeFile Offset、File Size这两组数据 。
    • VM Address、VM Size记录这此段在虚拟内存的位置和大小

    • File Offset、File Size记录着此段在Mach-O中的位置和大小。

image
    1. Data区,存放着各段数据
三、未使用ASLR时,Mach-O文件是如何载入内存的?
    1. 我们观察Mach-O文件的Load Commands各段的描述信息,重点观察VM Address、VM SizeFile Offset、File Size,就可以知道Mach-O在虚拟内存中是如何存放的了
    1. 例如下图中的LC_SEGMENT_64(__PAGEZERO)段,我们可以看出来,PAGEZERO段在虚拟内存中是从0x0开始,大小是0x100000000PAGEZERO段在Mach-O中是从0x0开始,大小是0x0,也就是说PAGEZERO在Mach-O中实际不存在数据 ,只有加载到内存后才会展开。

      image
    1. 例如下图中的__TEXT段,我们可以看出来,TEXT段在虚拟内存中从0x10000000开始,大小是0x6B8C000TEXT段在Mach-O中从0x0开始,大小是0x6B8C000

      image
    1. 例如下图中的__DATA段 ,我们可以看出来,DATA段在虚拟内存中从0x106B8C000开始,大小是0x1820000TEXT段在Mach-O中从0x6B8C000开始,大小是0x1350000

      image
    1. 综上所述 ,我们就可以绘制出比较直观的图来表示,如下图所示:
image
四、使用ASLR时,Mach-O文件是如何载入内存的?
    1. ASLR会在虚拟内存中的最开始的位置产生了随机偏移量,每次载入内存时,偏移量都会不同,导致后续各段的地址都会增加此偏移量,以增加攻击者预测地址的难度
    1. 例如:ASLR产生的随机偏移量是0x5000,那么Mach-O载入虚拟内存中后,就如下图所示:

      image
    1. 苹果从iOS 4.3之后引入了ASLR技术,也就说把Mach-O载入内存后,所有的地址都会经过ASLR偏移
    1. 我们使用MachOView工具静态分析看到的VM Address,是未经过ASLR偏移的虚拟内存地址,也就是说MachOView中的VM Address加上ASLR偏移量才是虚拟内存中的真正地址
    1. 总结一下:
    • Hopper、IDA、MachOView等工具中的看到的地址都是未使用ASLR的VM Address,所以要想知道虚拟内存中真正地址,就需要再加上ASLR偏移量

    • 虚拟内存中的真正地址 = ASLR偏移量 + PAGEZERO的大小 + Mach-O中的偏移量 (其中PAGEZERO的大小就是VM Size,Mach-O中的偏移量就是Offset,因为PAGEZERO到内存中才会展开,所以要加上PAGEZERO的大小)

    • 虚拟内存中的真正地址 = ASLR偏移量 + 用工具看到的VM Address

    • 虚拟内存中的真正地址 = ASLR偏移量 + Mach-O文件中的VM Address

    • Mach-O文件中的File Offset = 虚拟内存中的真正地址 - ASLR偏移量 - PAGEZERO的大小

    1. 想要得到ASLR偏移量,可以在使用LLDB命令image list -o -f | grep Mach-O文件名称,来查看加载到内存中的ASLR偏移量。(注意:ASLR是随机的,每次加载到内存都不一样)
五、如何给别人的App打断点,进行调试
    1. 按照上一篇的方法,进入到WeChat的lldb环境
    1. 使用LLDB指令image list -o -f | grep Mach-O文件名称,获取ASLR偏移量,如下图所示,本次载入内存的偏移量是0x27c0000

      image
    1. 用Hopper等工具静态分析出某个函数在虚拟内存中的函数地址,这里的函数地址是静待分析出来的,也就是未经过ASLR偏移的地址,并不能直接用于打断点,如下图所示,-[BaseMsgContentViewController SendTextMessageToolView:]函数的未ASLR偏移的函数地址是0x1002aec90

      image
    1. 使用LLDB指令breakpoint set -a 函数地址,给某个函数打断点,这里的函数地址指的是虚拟内存中的真实函数地址,也就是说这里的函数地址,是ASLR偏移量+静待分析的函数地址,也就是0x1002aec90+0x27c0000,完整的LLDB指令就是 breakpoint set -a 0x1002aec90+0x27c0000,如下图所示:

      image
    1. 打完断点之后,当App触发此断点时,就会卡住,以便我们输入LLDB调试,例如,我这里对微信的发送消息的方法-[BaseMsgContentViewController SendTextMessageToolView:]打了断点,之后每次发消息时都会卡主,以便我们继续输入LLDB指令调试
    1. 如果不想要断点了,可以用breakpoint delete 断点编号删除断点;可以通过breakpoint list命令,列出所有的断点编号
获取ASLR

iOS上我们可以直接使用dyld.h中的方法_dyld_get_image_vmaddr_slide来获取image虚拟地址的偏移量。_dyld_get_image_vmaddr_slide函数原型如下:

extern intptr_t   _dyld_get_image_vmaddr_slide(uint32_t image_index);

一般使用方法如下:

uint32_t c = _dyld_image_count();
    for (uint32_t i = 0; i < c; i++) {
     intptr_t index  = _dyld_get_image_vmaddr_slide(i);
    }
哪里使用?

ALSR决定了虚拟地址在内存中会发生的偏移量。例如一个 segment_command_64 的vmaddr 字段决定了其在虚拟内存中的地址。但是其真正的内存地址则是vmaddr + ALSR。

在fishhook中计算程序基址,使用动态符号表的虚拟地址和偏移来计算基址,具体算法如下:
基址 = __LINKEDIT.VM_Address - __LINK.File_Offset + silde的改变值
其中的silde就是由于ALSR所产生的随机偏移量。

签名机制

Bang's Blog:iOS App 签名的原理
iOS 签名机制

目的

先来看看苹果的签名机制是为了做什么。在 iOS 出来之前,在主流操作系统(Mac/Windows/Linux)上开发和运行软件是不需要签名的,软件随便从哪里下载都能运行,导致平台对第三方软件难以控制,盗版流行。苹果希望解决这样的问题,在 iOS 平台对第三方 APP 有绝对的控制权,一定要保证每一个安装到 iOS 上的 APP 都是经过苹果官方允许的,怎样保证呢?就是通过签名机制。

一、 通过App Store安装

iOS 系统相关复习_第5张图片
image.png
  1. 由苹果生成一对公私钥,公钥内置与iOS设备中,私钥由苹果保管。

  2. 开发者上传App给苹果审核后,苹果用私钥对App数据进行签名,发布至App Store。

  3. iOS设备下载App后,用公钥进行验证,若正确,则证明App是由苹果认证过的。

二、通过Xcode安装(真机调试)

由于不需要提交苹果审核,所以苹果没办法对App进行签名,因此苹果采用了双重签名的机制。Mac电脑有一对公私钥,苹果还是原来的一对公私钥。

iOS 系统相关复习_第6张图片
image.png
  1. 开发时需要真机测试时,需要从钥匙串中的证书中心创建证书请求文件(CSR),并传至苹果服务器。

  2. Apple使用私钥对 CSR 签名,生成一份包含Mac公钥信息及Apple对它的签名,被称为证书(CER:即开发证书,发布证书)。

  3. 编译完一个App后,Mac电脑使用私钥对App进行签名。

  4. 在安装App时,根据当前配置把CER证书一起打包进App。

  5. iOS设备通过内置的Apple的公钥验证CER是否正确,证书验证确保Mac公钥时经过苹果认证的。

  6. 再使用CER文件中Mac的公钥去验证App的签名是否正确,确保安装行为是经过苹果允许的。

苹果只是确定这里的安装行为是否合法,不会验证App内容是否修改。

注: 证书请求文件(CertificateSigningRequest.certSigningRequest),用于绑定电脑,文件中应该有Mac电脑的公钥。

三、通过Ad-Hoc正式打包安装

Xcode打包App生成ipa文件,通过iTunes或者蒲公英等第三方发布平台,安装到手机上。流程步骤基本和真机调试相同,差别在于第4步:

  1. 开发时需要打包测试或发布时,需要从钥匙串中的证书中心创建证书请求文件(CSR),并传至苹果服务器。

  2. Apple使用私钥对 CSR 签名,生成一份包含Mac公钥信息及Apple对它的签名,被称为证书(CER:即开发证书,发布证书)。

  3. 编译完一个App后,Mac电脑使用私钥对App进行签名。

  4. 编译签名完之后,要导出ipa文件,导出时,需要选择一个保存的方法(App Store/Ad Hoc/Enterprise/Development),就是选择将上一步生成的CER一起打包进App。

  5. iOS设备通过内置的Apple的公钥验证CER是否正确,证书验证确保Mac公钥是经过苹果认证的。

  6. 再使用CER文件中Mac的公钥去验证App的签名是否正确,确保安装行为是经过苹果允许的。

四、In-House企业版证书打包

企业版证书签名验证流程和Ad-Hoc差不多。只是企业版不限制设备数,而且需要用户在iOS设备上手动点击信任证书。

附加一些东西

通过真机调试安装和证书打包安装,不加限制,可能会导致被滥用(不通过App Store,只通过第三方发布平台就能安装),因此苹果加了两个限制:在苹果注册过的设备才可以安装;签名只针对某一个App。

在上述第4步,打包证书进App中时,还需要加上允许安装的设备ID和App对应的APPID等数据(Profile文件)。

根据数字签名的原理,只要数字签名通过验证,第 5 步这里的设备 IDs / AppID / Mac公钥 就都是经过苹果认证的,无法被修改,苹果就可以限制可安装的设备和App,避免滥用。

苹果还要控制iCloud/push/后台运行等,这些都需要苹果授权签名,苹果把这些权限开关统称为:Entitlements,去让苹果授权。

因此证书中可能包含很多东西,不符合规定的格式规范,所以有了Provisioning Profile(描述文件),描述中包含了证书以及其他所有的信息及信息的签名。

四种签名方式的区别

iOS 系统相关复习_第7张图片
image.png

总结一下最终流程

iOS 系统相关复习_第8张图片
image.png

1、在你的 Mac 开发机器生成一对公私钥,这里称为公钥L,私钥L。L:Local
2、苹果自己有固定的一对公私钥,私钥在苹果后台,公钥在每个 iOS 设备上。这里称为公钥A,私钥A。A:Apple
3、把公钥 L 传到苹果后台,用苹果后台里的私钥 A 去签名公钥 L。得到一份数据包含了公钥 L 以及其签名,把这份数据称为证书(cer)。
4、在苹果后台申请 AppID,配置好设备 ID 列表和 APP 可使用的权限,再加上第③步的证书,组成的数据用私钥 A 签名,把数据和签名一起组成一个 Provisioning Profile 文件,下载到本地 Mac 开发机。
5、在开发时,编译完一个 APP 后,用本地的私钥 L 对这个 APP 进行签名,同时把第④步得到的 Provisioning Profile 文件打包进 APP 里,文件名为 embedded.mobileprovision,把 APP 安装到手机上。
6、在安装时,iOS 系统取得证书,通过系统内置的公钥 A,去验证 embedded.mobileprovision 的数字签名是否正确,里面的证书签名也会再验一遍。
确保了 embedded.mobileprovision 里的数据都是苹果授权以后,就可以取出里面的数据,做各种验证,包括用公钥 L 验证APP签名,验证设备 ID 是否在 ID 列表上,AppID 是否对应得上,权限开关是否跟 APP 里的 Entitlements 对应等。

其他人想要编译签名App时应怎么做?

简单就是把私钥给他。私钥也是从 钥匙串 中导出,就是.p12文件,其他Mac导入私钥后就可以正常使用了。

查看ipa包中注册的设备ID

解压.ipa文件,得到App数据包,显示包内容,找到embedded.mobileprovision文件所在目录,运行命令security cms -D -i embedded.mobileprovision

iOS 系统相关复习_第9张图片
image.png

你可能感兴趣的:(iOS 系统相关复习)