iOS逆向与安全:基础篇

前言

从本篇文章开始,笔者会整理iOS逆向相关的笔记。作为一位新人,希望通过整理笔记能够更好的理解和掌握知识。能力有限,在行文时难免出现错误欢迎批评和指正。

前期准备:

  • 一台越狱的iOS设备,笔者使用iPad mini2系统iOS9.2.1(推荐使用iOS9.1或者可以完美越狱的系统)
  • 一台电脑,笔者使用的是MacBook Pro
  • 一条数据线

如何越狱

网上有很多教程帮助你来越狱,这里推荐两个网站:

注意:在购买iOS设备时直接购买对应版本的系统,由于苹果官方关闭降级通道所以无法刷固件降级。

越狱成功后,桌面上会出现一个叫做Cydia的应用,它是越狱后的App Store可以安装各种第三方的软件,比如:插件、补丁、APP等。该应用的作者Jay Freeman(saurik),一位骨灰级大神。

必装插件或补丁

  • Apple File Conduit 2:可以访问整个iOS设备的文件系统。作者是Jay Freeman
  • AppSync unified: 可以绕过系统验证,随意安装、运行破解的IPA安装包。
  • PP助手: 一个应用商店,可以自由安装海量APP。
  • OpenSSH: 用于远程登录iOS设备。

Mac 必备

  • iFunBox: 管理文件系统。
  • PP助手:一个应用商店。
  • iTerm2: 一款优秀的命令行工具
  • Alfred: 便捷搜索,自定义工作流,调高效率的工具

配置远程登录

SSH:Secure Shell的缩写,安全外壳协议,是一种可以为远程登录提供安全保障的协议。使用SSH可以把所有传输的数据进行加密,防止中间人攻击、DNS欺骗和IP欺骗。
OpenSSH: 是SSH协议的免费开源实现。通过它让Mac登录iOS设备,在Cycript中搜索安装。

使用密码登录

如果iOS设备上已经安装了OpenSSH,此时就可以通过Mac终端登录iOS设备。前提,iOS设备和Mac需要处于相同WiFi环境下。在设置中查看iOS设备的IP地址。打开终端输入:

1
2
ssh root@iOS设备IP地址
// 回车,会提示时候建立连接输入yes即可。之后提示输入密码,默认密码是`alpine`。

iOS设备上有两个默认账号:root,mobile。

  • root:最高权限账户,$HOME是/var/root。或者通过终端输入pwd查看。
  • mobile:普通权限账户,只能操作一些普通文件,$HOME是/var/mobile
  • mobile登录方式:ssh mobile@服务器地址。初始密码alpine
  • 修改root密码:passwd,修改mobile用户密码:passwd mobile

公钥登录

上面的密码登录方式比较麻烦,在每次登录时都需要输入密码。SSH提供了公钥登录,以省去密码的步骤。下面来看看公钥登录流程:
将电脑端的SSH公钥保存在iOS设备中,登录时iOS设备端会向电脑端发送一个随机字符串,登录用户通过自己的私钥加密后发送给iOS端,iOS端通过事先存储好的公钥解密,如果解密成功,证明用户是可信的。

查看$HOME/.ssh/目录,是否有id_rsa私钥和id_rsa.pub公钥,如果没有使用ssh-keygen生成。

上传公钥到远程设备,通过ssh-copy-id命令。

1
ssh-copy-id -i $HOME/.ssh/id_rsa.pub root@地址

该条命令的作用是将id_rsa.pub公钥追加到iOS设备的$HOME/.ssh/authorized_keys文件中。

1
cat $HOME/.ssh/authorized_keys //查看文件内容

通过USB登录设备

在WiFi不稳定的情况下,通过WiFi登录会非常的卡顿。可以通过接口转发的方法,使用USB SSH登录,保证连接的稳定性。

方法很简单,首先安装libimobiledevice,然后使用里面提供iproxy把本地端口(例如:2222)映射到设备的TCP端口22,就可以通过本地的2222端口建立连接了。

1
2
3
brew install libimobiledevice
iproxy 2222 22
ssh root@localhost -p 2222

如果,每次输入iproxy 2222 22进行接口转换比较麻烦,可以将其配置到电脑的开机启动项中。创建~/Library/LaunchAgents/com.usbmux.iproxy.plist,填入一下:

运行命令launchctl load ~/Library/LaunchAgents/com.usbmux.iproxy.plist使之生效。

每次输入ssh root@localhost -p 2222也很麻烦,可以通过指定一个名称进行SSH连接,打开$HOME/.ssh/config文件,没有创建一个,写入一下内容。

1
2
3
4
Host ipad 
Hostname localhost
User root
Port 2222

通过以上配置,在终端输入ssh ipad即可登录iOS设备了。

砸壳

App Store下载应用都会进行加密,破解加密称为砸壳。本节记录在学习砸壳中遇到的问题和解决方法。

三种砸壳方案:

这三种方案网上很多教程,下面整理的是第三种方案简单高效。

配置环境

iOS设备和电脑都需安装Frida

iOS设备安装Frida,打开Cydia添加软件源 https://build.frida.re,之后搜索Frida安装。电脑端通过pip命令sudo pip install frida安装。

接下来,克隆**frida-ios-dump**库到本地。仓库有两个分支分别对应python2.xpython3.x,需要根据本地Python版本来判断。

1
2
3
// Mac终端
python -V
// 笔者:Python 3.6.3 :: Anaconda custom (64-bit)

由于笔者配置的Python 3.x,切换到3.x分支安装firda-ios-dump依赖。在frida-ios-dump仓库目录下运行下面命令。

1
sudo pip install -r requirements.txt --upgrade

安装结束后,将越狱设备通过USB连上电脑进行端口映射:

1
iproxy 2222 22

到此配置结束,如果一切顺利就可以进行砸壳了。

进行砸壳

frida-ios-dump目录下,使用命令./dump.py -l查看应用名称和Bundle ID,通过命令./dump.py + 程序名字 or BundleID即可砸壳。当程序执行完,在当前目录中会出现一个xxx.ipa

不过,通往成功的道路永远不是一帆风顺的,在运行./dump.py命令是出现了错误Waiting for USB device…。在这个issue中给出了解决方案。

推荐文章一条命令完成砸壳

Reveal配置

Reveal是一款用于查看程序界面结构和调试界面的工具,可以在开发中动态修改调试代码修改程序的样式,也可以注入到第三方APP查看应用的界面结构。

本节将会学习如何在越狱设备和非越狱设备上查看应用的结构。

越狱设备集成Reveal

笔者在越狱设备集成Reveal遇到了一个问题,新版的RevealServerFramework而旧版的是dylib,两者集成方式不同,但是网上很多资料都是过时的,所以这里给出新版的集成方式。

首先在Cydia下载Reveal2Loader插件。插件安装后进入设置找到Reveal-->Enabled Applications选择要查看的App。例如:简书

电脑端安装Reveal软件,正常情况下,打开手机上的简书APP会在Reveal中看到下图,点击进入即可查看界面结构。

有时会出现RevealServer.framework版本问题,电脑端点击Help->Show Reveal Library in Finder Framework拷贝到手机Library/Frameworks目录下替换原来的。

非越狱设备集成Reveal

如果在开发中集成Reveal直接使用CocoaPods即可,在Podfile文件加入以下内容:

1
2
//只在Debug模式下开启
pod 'Reveal-SDK', :configurations => ['Debug']

之后运行pod install即可集成。

如果想在非越狱设备查看其它App的界面结构,需要使用**MonkeyDev**工具辅助完成。
首先安装MonkeyDev,安装在GitHub仓库有文档。

非越狱设备集成Reveal还需要目标App的脱壳ipa包。具体步骤查看文章:

Cycript配置

Cycript是允许开发者使用Objective-C++Javascript组合语法查看及修改运行时APP内存信息的工具。

在越狱设备上安装

Cycriptsaurik提供的工具,在Cydia中搜索Cycript并安装即可。

安装完成后,用SSH登录设备输入cycript命令,就可以进入交互界面。

在越狱设备上,可以使用一些命令注入目标进程,调试目标函数。下面例子:脚本注入到SpringBoard(桌面)中,使其弹出一个提示框。

1
2
3
4
iPad:~ root# cycript -p SpringBoard
cy# var alert = [[UIAlertView alloc] initWithTitle:@"Hello" message:@"Hello, wold!" delegate:nil cancelButtonTitle: @"cancel" otherButtonTitles: nil]
#"<UIAlertView: 0x1348bd1f0; frame = (0 0; 0 0); layer = <CALayer: 0x1321c89b0>>"
cy# [alert show]

这是《iOS应用逆向与安全》中给出的一个简单的例子。

Cycript分析应用

上面例子中使用了一条命令cycript -p SpringBoard,通过该命令可以调试指定APP。该命令有两种使用方式,除了上面的cycript -p 程序名称,还可以通过cycript -p 程序进程ID。注意这里的程序的名称并不是APP的名称。

查看程序的名称和进程ID可以通过安装一个插件adv-cmds。通过它使用ps命令查看当前运行程序的进程ID及可执行文件的路径。

安装完成后,通过ps -A命令查看当前设备的进程ID和可执行文件的路径。同时,支持关键词搜索ps -A | grep 关键词

例如,查看简书的路径和进程ID

简书

由于程序的进程ID是会发生变换的推荐使用名称。

下面就以简书为例,学习如何使用cycript的使用。

查看应用信息

1
2
3
4
5
cy# NSHomeDirectory() // 沙盒路径
cy# NSBundle.mainBundle().bundleIdentifier //BundleID
cy# NSBundle.mainBundle().bundlePath //mainBundlePath
cy# NSSearchPathForDirectoriesInDomains (NSDocumentDirectory , NSUserDomainMask , YES)[0] // Document Path
cy# NSSearchPathForDirectoriesInDomains (NSCachesDirectory , NSUserDomainMask , YES)[0] // cache Path

查看APPApplication信息,例如:查看简书的Application对象,为自定义的HGApplication

1
2
cy# UIApp 
# "<HGApplication: 0x154d49910>"

查看rootViewController

1
2
cy# UIApp.keyWindow .rootViewController
#"<HGTabBarController: 0x155006200>"

还可以使用地址查看信息。例如:查看HGTabBarControllerview

1
2
cy# #0x155006200.view
#"<UILayoutContainerView: 0x154fca480; frame = (0 0; 1024 768); autoresize = W+H; layer = <CALayer: 0x154f65a20>>"

另外Cycript提供了choose()函数选择某个类的实例数组。例如:学习简书中所有的Button类的所有实例数组。

1
2
3
cy# choose(UIButton)
[#"<UIButton: 0x157210600; frame = (0 8.5; 685 16); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x15705f020>>",
#"<HGAvatarButton: 0x15724ead0; baseClass = UIButton; frame = (16 14.5; 45 45); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x1572088b0>>".....]

以上是cycript的基本用法。

cycript高级用法

每次要输入那么多的代码是在有点麻烦,能不能将常用的代码封装起来呢?答案是肯定的。

Cycript本身是支持加载自己的脚本的,可以通过@import命令加载。例如:加载mjscript脚本。

1
cy# @import mjcript

关于如何将自己学的脚本添加到手机上查看【越狱-逆向】基于Cycript实现的一些实用函数文档,对如何添加,如何使用写的比较详细,这里就不再重复赘述。

Class-dump

本节学习如何使用工具导出第三方App的头文件,学习前需要补充点基础知识。

Mach-O文件

想让程序在设备上运行起来,需要将写好的代码生成可执行文件这样才能被操作系统所理解。比如,在Linux下可执行文件的格式是ELF,在Window中可执行文件是PE32/PE32+,而在MACiOS中是Mach-O格式。

Mach-O的组成结构如下图:

图片来自《Mach-O 文件格式探索》

可以看的出 Mach-O 主要由 3 部分组成:

  • Mach-O 头(Mach Header):这里描述了 Mach-O 的 CPU 架构、文件类型以及加载命令等信息;
  • 加载命令(Load Command):描述了文件中数据的具体组织结构,不同的数据类型使用不同的加载命令表示;
  • 数据区(Data):Data 中每一个段(Segment)的数据都保存在此,段的概念和 ELF 文件中段的概念类似,都拥有一个或多个 Section ,用来存放数据和代码。

如何获取Mach-O文件,在App Store中下载应用.ipa改成.zip格式,之后解压缩会在里面发现一个应用同名的文件。
下面是简书的Mach-O文件:

Class-dump使用

class-dump的作用就是把Mach-O文件的class信息导出来生成头文件。

下载后将其中的class-dump拷贝到/usr/local/bin目录下,就可以在终端使用class-dump命令。

终端输入class-dump

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class-dump 3.5 (64 bit) (Debug version compiled Sep 17 2017 16:24:48)
Usage: class-dump [options] <mach-o-file>

where options are:
-a show instance variable offsets
-A show implementation addresses
--arch <arch> choose a specific architecture from a universal binary (ppc, ppc64, i386, x86_64, armv6, armv7, armv7s, arm64)
-C <regex> only display classes matching regular expression
-f <str> find string in method name
-H generate header files in current directory, or directory specified with -o
-I sort classes, categories, and protocols by inheritance (overrides -s)
-o <dir> output directory used for -H
-r recursively expand frameworks and fixed VM shared libraries
-s sort classes and categories by name
-S sort methods by name
-t suppress header in output, for testing
--list-arches list the arches in the file, then exit
--sdk-ios specify iOS SDK version (will look for /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS<version>.sdk
or /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS<version>.sdk)
--sdk-mac specify Mac OS X version (will look for /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX<version>.sdk
or /Developer/SDKs/MacOSX<version>.sdk)
--sdk-root specify the full SDK root path (or use --sdk-ios/--sdk-mac for a shortcut)

安装成功,可以解析头文件方法很简单,常用指令。

class-dump -H mach-o路径 -o 头文件存储路径

以简书App为例,使用之前砸壳的ipa文件,找到里面的Mach-O文件。

1
class-dump -H Hugo -o ./Header

在当前目录中会生成Header文件夹,里面包含所有的头文件。

参考文章