非越狱逆向开发总结文档(含iOS Extension)

在经过越狱开发之后,我开始了接触非越狱开发。

相比之下,他们之间有很多相似点:比如都是通过砸壳以后的IPA包去dump头文件来进行分析,分析代码逻辑之后去hook对我们有用的类做一些自己的事情,封装成动态库后注入到包内的二进制Mach-O文件中,在程序dyld的时候加载到程序的内存中运行。


而不同的是非越狱情况下会出现很多权限的限制,不能够对一些系统级别的环境(Springboard之类)去做修改,同样苹果在APPStore也有一套自己的加密机制,而要让自己注入的APP能够在非越狱机型上运行,就还需要对苹果的签名机制做一些修改。

下面根据我学习的一些情况对非越狱开发的整个步骤和一些细节做一个总结。现在来说,已经有很多大神对非越狱方面的开发做了许多的贡献,比如iOSOpenDev(https://github.com/Urinx/iOSAppHook )以及MonkeyDev(https://github.com/AloneMonkey/MonkeyDev ),这两个框架集成了很多功能,比如很好用的Tweak编译,LLDBTools动态调试以及fishhook等都是逆向必备的。对于一般的应用可以省去很多中间繁琐的步骤,不过同样也存在不能够去逆向PlugIns以及Watch等APP Extension的遗憾。为什么会这样呢,要分析的话,就需要从非越狱逆向的步骤开始熟悉。

应用解密-砸壳

应用上传至APP Store后,苹果会对应用的代码部分进行加密,当应用运行时才会动态解密,在这样的情况下是无法直接使用class-dump和IDA进行代码分析的。所以,在分析应用之前,要把应用加密的内容解密,然后把应用和解密后的可执行文件导出到计算机中。
以下有三种方法进行解密:
- 使用dumpdecrypted动态注入后,dump内存中解密后的代码部分。
- 使用Clutch,通过调用posix_spawnp生成一个进程,然后暂停进程,dump内存
- 使用Frida,跟上面dumpdecrypted类似

需要注意的是,这些工具需要通过越狱手机在越狱环境下才能够实现。

下面简单介绍一下frida-ios-dump的用法:

该工具基于frida提供的强大功能通过注入js实现内存dump然后通过python自动拷贝到电脑生成ipa文件,通过以下方式配置完成之后真的就是一条命令砸壳。

环境配置

首先上面也说了该工具基于frida,所以首先要在手机和mac电脑上面安装frida,安装方式参数官网的文档:(https://www.frida.re/docs/home/
越狱手机在Cydia中安装Frida Server:

Mac上面使用如下命令安装即可:
sudo pip install frida –upgrade –ignore-installed six
然后将越狱设备通过USB连上电脑进行端口映射:
iproxy 2222 22
到此环境就配置好了,接下来就可以一键砸壳了!

一键砸壳

最简单的方式直接使用./dump + 应用显示的名字即可,如下:
这里写图片描述
完成之后可以用otool来查看,otool可以输出app的load commands,然后通过查看cryptid这个标志位来判断app是否被加密。1代表加密了,0代表被解密了:
这里写图片描述
鉴于百度输入法是一个多targets的应用,包含一个Watch以及 App Extension。所以同理,我们还需要依次确认以下二进制文件,这里就跳过了。

注入动态库

在进行一顿分析class-dump头文件以及IDA导出来的伪代码之后,相应的就可以去构建自己的动态库了,动态库中主要就是通过hook应用中已有的代码来完成对动态库的修改。下面来介绍注入动态库的两种工具:
一个是yololib工具,一个是insert_dylib工具;
在学习dyld之后,我们知道当应用加载的文件的时候,这个文件就是一个Mach-O类型的通用二进制文件:
这里写图片描述
我们重点关注Load Command字段,Load Command告诉系统应当如何加载文件中的数据,对系统内核加载器核动态链接器起指导作用。其中LC_LOAD_DYLIB代表的是依赖的动态库,包括动态库名称、当前版本号、兼容版本号。可以使用otool -L xxx命令来查看。
上面介绍的两款工具也是通过修改LC_LOAD_DYLIB来增加注入动态库。

而注入的本质是往Mach-O文件里添加动态库的名称和路径:
这里写图片描述
这里的路径为相对路径,下面介绍一下相对路径以及之间的区别:
在使用otool -L命令查看可执行文件中加载的动态库时,一般会看到@rpath、@loader_path、@executable_path,这里分别解释一下这些变量的意义:

  • @executable_path:表示可执行程序所在的目录,一般是xxx.app目录,这里特别注意的是:在iOS Extension的二进制文件中它代表的是xxx.app/PlugIns/xxx.appex目录。

  • @loader_path:表示每一个被加载的为二进制文件的目录。例如,xxx.plugin/aaa/bbb依赖xxxx.plugin/ccc/ddd.framework,都能找到ddd.framework。

  • @rpath:这个变量是在Xcode build里面设置的。如果将动态库的Dynamic Library Install Name设置为@rpath/xxx/xxx,就可以在使用的工程中设置一个或多个Runpath Search Paths来指定搜索路径。在运行时,会将@rpath分别替换为Runpath Search Paths中指定的路径来查找动态库。

yololib来注入路径:

/usr/local/yololib xxx APPHook.framework/APPHook

!需要注意的是yololib默认的相对路径是@executable_path

也同样可以用insert_dylib来注入动态库:

insert_dylib [@executable_path](APPHook.framework/APPHook) xxx

它就可以自定义注入的相对路径了。

重签名

iOS 签名机制挺复杂,各种证书,Provisioning Profile,entitlements,CertificateSigningRequest,p12,AppID,概念很多,也很容易出错。

目的

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

签名实现方式

苹果官方生成一对公私钥,在 iOS 里内置一个公钥,私钥由苹果后台保存,我们传 App 上 AppStore 时,苹果后台用私钥对 APP 数据进行签名,iOS 系统下载这个 APP 后,用公钥验证这个签名,若签名正确,这个 APP 肯定是由苹果后台认证的,并且没有被修改过,也就达到了苹果的需求:保证安装的每一个 APP 都是经过苹果官方允许的。

如果我们 iOS 设备安装 APP 只有从 AppStore 下载这一种方式的话,这件事就结束了,没有任何复杂的东西,只有一个数字签名,非常简单地解决问题。

但实际上因为除了从 AppStore 下载,我们还可以有三种方式安装一个 App:

  • 开发 App 时可以直接把开发中的应用安装进手机进行调试。
  • In-House 企业内部分发,可以直接安装企业证书签名后的 APP。
  • AD-Hoc 相当于企业分发的限制版,限制安装设备数量,较少用。
    苹果要对用这三种方式安装的 App 进行控制,就有了新的需求,无法像上面这样简单了。

几个相关的名词概念

APP ID

在苹果官方的开发者计划(Apple Developer Member Center)层面,App ID 即 Product ID,用于标识一个或者一组 App。

在 Mac/iOS 开发语境中,bundle(捆绑) 是指一个内部结构按照标准规则组织的特殊目录。在 Mac OS 应用程序目录下的某个 *.app 上可右键显示包内容(Contents),其本质上就是可执行二进制文件(MacOS/)及其资源(Resources/)的打包组合。因此,在 Xcode 中配置的 Bundle Identifier 必须和 App ID 是一致的(Explicit)或匹配的(Wildcard)。

App ID 字符串通常以反域名(reverse-domain-name)格式的 Company Identifier(Company ID)作为前缀(Prefix/Seed),一般不超过 255 个 ASCII 字符。

设备ID(UDID)

Device 就是运行 iOS 系统用于开发调试 App 的设备。每台 Apple 设备使用 UDID (Unique Device Identifier)来唯一标识。
iOS 设备连接 Mac 后,可通过 iTunes->Summary 或者 Xcode->Window->Devices 查看其 UDID。

iOS(开发)证书

iOS 证书是用来证明 iOS App 内容(bundle with executable and resources)的合法性和完整性的数字证书。对于想安装到真机或发布到 AppStore 的应用程序(App),只有经过签名验证(Signature Validated)才能确保来源可信,并且保证 App 内容是完整、未经篡改的。
Apple 证书颁发机构 WWDRCA(Apple Worldwide Developer Relations Certification Authority) 将使用其 private key 对 CSR 中的 public key 和一些身份信息进行加密签名生成数字证书(ios_development.cer)并记录在案(Apple Member Center)。
从 Apple Member Center 网站下载证书到 Mac 上双击即可安装(当然也可在 Xcode 中添加开发账号自动同步证书和[生成]配置文件)。证书安装成功后,在 KeychainAccess|Keys 中展开创建 CSR 时生成的 Key Pair 中的私钥前面的箭头,可以查看到包含其对应公钥的证书(Your requested certificate will be the public half of the key pair.);在 Keychain Access|Certificates 中展开安装的证书(ios_development.cer)前面的箭头,可以看到其对应的私钥。
这里写图片描述

供应配置文件(Provisioning Profiles)

Provisioning Profile 文件包含了上述的所有内容:证书、App ID、UDID以及entitlements,后缀名是.mobileprovision。
一个 Provisioning Profile 对应一个 Explicit App ID 或 Wildcard App ID(一组相同 Prefix/Seed 的 App IDs)。在网站上手动创建一个 Provisioning Profile 时,需要依次指定 App ID(单选)、证书(Certificates,可多选)和设备(Devices,可多选)。用户可在网站上删除(Delete)已注册的 Provisioning Profiles。
这里写图片描述

这是一个Provisioning Profiles预览样式。

如果要打包到真机上运行一个APP,一般要经历以下三步:

  • 首先,需要指明它的 App ID,并且验证 Bundle ID 是否与其一致;
  • 其次,需要证书对应的私钥来进行签名,用于标识这个 APP 是合法、安全、完整的;
  • 然后,如果是真机调试,需要确认这台设备是否授权运行该 APP。

安全机制和签名

苹果应用的安全机制大概是这样的,也是靠这样来控制一些APP的安装和使用,避免滥用的行为:
这里写图片描述
公钥1其实就是证书请求文件(csr文件),公钥1是通过私钥1产生的,申请后下载下来证书后双击安装,Mac电脑中的私钥会和下载下来的证书相关联,这时候会在钥匙串中看到对应的私钥。然后在通过Xcode打包的时候,使用私钥1对这个二进制文件进行加密,同时使用到的证书也被打包到ipa文件中。在手机进行安装前,会首先用公钥2对证书进行解密,通过解密拿到证书中的公钥1,由于需要安装的二进制文件是通过私钥1加密的,所以使用公钥1进行解密,这个流程可以确保手机安装的app都是通过苹果授权允许的,从而确保了安全性。

在Xcode中,它是可以让我们自行配置的,然后它会自动去对应:
这里写图片描述
Xcode 中配置的 Code Signing Identity(entitlements、certificate)必须与 Provisioning Profile 匹配,并且配置的 Certificate 必须在本机 Keychain Access 中存在对应 Public/Private Key Pair,否则编译会报错。

Xcode 所在的 Mac 设备(系统)使用 CA 证书(WWDRCA.cer)来判断 Code Signing Identity 中 Certificate 的合法性:

  • 若用 WWDRCA 公钥能成功解密出证书并得到公钥(Public Key)和内容摘要(Signature),证明此证书确乃
    AppleWWDRCA 颁布,即证书来源可信;
  • 再对证书本身使用哈希算法计算摘要,若与上一步得到的摘要一致,则证明此证书未被篡改过,即证书完整。

可以看到在我们开发应用的时候,整个CodeSign过程都是在Xcode编译的时候完成的: 这里写图片描述

在这里也给我们提供了一个思路,就是通过Xcode来帮助我们进行签名处理,这个也是MonkeyDev的实现方式。

那为什么要重签呢?其实就是上面的安全机制在起作用,在我们要将程序打包成ipa包后,ipa包中会包括PP文件(Provisioning Profile)和_CodeSignature等文件,里面包括了对整个ipa的签名信息。一旦改动ipa中的任何内容,例如增加一个头文件,签名就会失效,导致ipa无法安装。因此,如果要通过修改ipa包中的内容来篡改信息或注入代码(例如加一个动态库进去)的话,就需要解决ipa包的重签名问题。

怎样去重签名呢?

系统中存在/usr/bin/codesign可以用来对二进制文件进行签名(同样也是Xcode用来签名的文件),其中二进制文件的Code Signature就是用来存放签名信息的:
codesign -fs #your identity# xxx
这里写图片描述
通过codesign我们还可以看到砸过壳越狱版本APP是被消除签名的:
codesign -dvvv xxx.app/xxx or xxx.app/PlugIns/xxx.appex/xxx
这里写图片描述
这样的签名可能还不能通过苹果的机制,现在还不得不介绍一下的是entitlements.plist,entitlement.plist其实就是描述一个App需要包含哪些权限,可以通过Xcode自动设置也可以自己到开发者网站上设置。
这里写图片描述
entitlements.plist是在PP文件里的,我们可以导出搜狗输入法的来看一下:

其中就包含了group和iCloud相关的信息,所以在签名的时候还要把entitlement文件带进去。

//  导出 entitlements
security cms -D -i embedded.mobileprovision > profile.plist
/usr/libexec/PlistBuddy -x -c 'Print :Entitlements' profile.plist > entitlements.plist 
//  对注入的 framework 和原二进制文件签名
codesign --entitlements entitlements.plist -f -s #your identity# xxx 

之后还要把相应的PP文件复制到相应的文件夹内,包括iOS Extension也是一样,这样整个签名就结束了。

在应用中存在Extension的时候,重签名的时候还需要注意一下几点:

  • 列表内容通过 mobileprovision 文件导出对应的 entitlements.plist,并将二者拷贝到对应的目录下
  • Plugins 目录下的每个文件需要用对应的证书单独签名,包含的 Framework 也用其证书签名
  • 用主文件的证书对主文件及Frameworks目录下的所有文件进行重签名
  • 我们需要确保在 Info.plist 中的 Bundle ID 是否跟我们的描述文件里面指定的一致。因为在重签的过程中,codesign会检查我们的 Info.plist 文件里面的 Bundle ID,如果不匹配则会返回一个错误值。
  • 根据之前结果来看,除了需要对app中的info.plist更改Bundle ID, 还需要对重签名的插件中的info.plist 也更改符合的Bundle ID

在上述的步骤都结束之后就可以打包安装了!!!

为什么MonkeyDev会不能逆向APP Extension呢?

首先来看一下MonkeyDev这个框架的实现方式:
这里写图片描述
MonkeyDev是在Xcode中添加了脚本来配置注入动态库和实现重签名的,通过路径找到实现脚本,我们看到脚本中有一行是删除了PlugIns文件夹:

既然这样我们就可以模仿它的实现方式去创造出跟他类似的功能,实现Xcode的自动化打包功能,也就是很好的利用了Xcode自带的重签名功能,非常方便。

实现类似于MonkeyDev的功能

1.在编译的时候添加脚本

2.替换原来$BUILT_PRODUCTS_DIR目录中的.app文件

# 拿到解压的临时APP的路径
TEMP_APP_PATH=$(set -- "$TEMP_PATH/Payload/"*.app;echo "$1")
TARGET_APP_PATH="$BUILT_PRODUCTS_DIR/$TARGET_NAME.app"
cp -rf "$TEMP_APP_PATH/" "$TARGET_APP_PATH/"

3.更新 Info.plist 里的BundleID

/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $PRODUCT_BUNDLE_IDENTIFIER" "$TARGET_APP_PATH/Info.plist"

4.重签第三方 Frameworks下已存在的动态库

/usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" "$FRAMEWORK"

5.注入编写的动态库

INJECT_FRAMEWORK_RELATIVE_PATH="APPHook.framework/APPHook"
${SRCROOT}/yololib "$TARGET_APP_PATH/$APP_BINARY" "$INJECT_FRAMEWORK_RELATIVE_PATH"

这样就可以很轻松的将自己编写的动态库注入到已知应用中去,并且通过设置Xcode的Signing实现自动签名的操作。

如果应用中存在PlugIns的话,拿搜狗输入法为例,我们可以在自己的工程中新建一个键盘的iOS Extension新Target,然后在Extension编译时添加脚本:
这里写图片描述

下面就是加入脚本之后的Xcode中的BuildLog,即可以在脚本里面实现注入的操作:
这里写图片描述

发布了71 篇原创文章 · 获赞 34 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/TuGeLe/article/details/81879686