iOS逆向笔记(二):iOS二进制文件类型


人活在世界上,快乐和痛苦本就分不清。所以我只求它货真价实。

——王小波 《革命时期的爱情》

iOS二进制文件类型

  • 在iOS逆向工程初学阶段,我们的目标主要是ApplicationDynamic Library(以下简称dylib)和Daemon这三类二进制文件,对它们的了解越深入,逆向工程就会越顺利。这三类文件分工不同,其目录结构和文件权限也有一些区别。

Application

  • Application就是我们最熟悉的App了。虽然对于大多数iOS开发者来说,工作都是在跟App打交道,但在iOS逆向工程中,关注App的侧重点与正向开发还是不尽相同的。了解下面的几个App相关概念,是开始逆向工程前的必备工作。

bundle

  • bundle的概念来源于NeXTSTEP,它不是一个文件,而是一个按某种标准结构来组织的目录,其中包含了二进制文件及运行所需的资源。正向开发中常见的Appframework都是以bundle的形式存在的;在越狱iOS中常见的PreferenceBundle,可以看成是一种依附于Settings的App,结构与App类似,本质也是bundle
  • Framework也是bundle,但frameworkbundle中存放的是一个dylib,而不是可执行文件。相对来说,framework的地位比App更高,因为一个App的绝大多数功能都是通过调用framework提供的接口来实现的。将某个bundle确立为逆向目标后,绝大多数逆向线索都可以在bundle内找到,这大大降低了逆向工程的复杂度。

App目录结构

  • 在iOS逆向工程中,对App目录结构的熟悉程度是决定工作效率的重要因素。App目录的以下三个部分比较重要。
Info.plist
  • Info.plist记录了App的基本信息,如bundle identifier可执行文件名图标文件名等。可以通过Xcode查看它的值,也可以通过Xcode自带的命令行工具plutil查看它的值,如下:
1
2
3
plutil -p /Users/snakeninny/Code/iOSSystemBinaries/8.1_iPhone5/SiriViewService.app/Info.plist | grep CFBundleIdentifier

"CFBundleIdentifier" => "com.apple.SiriViewService"
可执行文件
  • 可执行文件的重要性不言而喻,它是App目录下最核心的部分,也是逆向工程最主要的目标。同样可以通过Xcode和plutil两种方式来查看Info.plist,定位可执行文件。用Xcode查看Info.plist的界面,也可以通过Xcode自带的命令行工具plutil查看它的值,如下:
1
2
snakeninny$ plutil -p /Users/snakeninny/Code/iOSSystemBinaries/8.1_iPhone5/SiriViewService.app/Info.plist| grep CFBundleExecutable
"CFBundleExecutable" => "SiriViewService"
lproj目录
  • lproj目录下存放的是各种本地化的字符串(.strings),是iOS逆向工程的重要线索,也可以用
    plutil查看,如下:
1
2
3
4
5
6
7
8
9
10
 plutil -p /Users/snakeninny/Code/iOSSystemBinaries/8.1_iPhone5/SiriViewService.app/en.lproj/Localizable.strings

{
"ASSISTANT_INITIAL_QUERY_IPAD" => "What can I help you
with?"
"ASSISTANT_BOREALIS_EDUCATION_SUBHEADER_IPAD" => "Just
say "Hey Siri" to learn more."
"ASSISTANT_FIRST_UNLOCK_SUBTITLE_FORMAT" => "Your
passcode is required when %@ restarts"
……

系统App VS StoreApp

  • /Applications/目录存放系统App和从Cydia下载的App(我们把来自Cydia的App视为系统App),而/var/mobile/Containers/目录存放的则是StoreApp。虽然两者都是App,但它们在如下方面存在着一些差别。
目录结构
  • 两种App的bundle内部目录结构区别不大,都含有Info.plist可执行文件lproj目录等,但是数据目录的位置不同:StoreApp的数据目录在/var/mobile/Containers/Data/下,以mobile权限运行的系统App的数据目录在/var/mobile/下,而以root权限运行的系统App的数据目录在/var/root/下。
安装包格式与权限
  • Cydia App的安装包格式一般是debStoreApp的安装包格式一般是ipa。其中deb是来自Debian的安装包格式,由Cydia作者saurik移植到iOS中,它的属主用户和属主组一般是root和admin,能够以root权限运行;而ipa是苹果为iOS推出的专属App安装包格式,属主用户和属主组都是mobile,只能以mobile权限运行。
沙盒(sandbox)
  • 通俗地说,iOS中的沙盒就是一种访问限制机制,我们可以把它看作是权限的一种表现形式,授权文件(entitlements)也是沙盒的一部分。它是iOS最核心的安全组件之一,其实现很复杂,这里不过多讨论其细节。总的来说,沙盒会将App的文件访问范围限制在这个App内部,一个App一般不知道其他App的存在,更别说访问它们了;沙盒还会限制App的功能,例如对iCloud接口的调用就必须经过沙盒的允许。
  • 在初学阶段,我们的目标不是沙盒,知道有这样一个东西存在就够了。在iOS逆向工程中,越狱本身已经破除了iOS的绝大多数安全限制,并对沙盒进行了一定程度的扩充,因此我们往往很容易忽略sandbox的存在,从而碰到一些看似很奇怪的问题。比如某个tweak不能写文件,调用了某个函数却没有出现应有的效果,在确保自己的代码没有问题的前提下,就要回过头来检查这些问题是不是因为权限不够,或者沙盒限制造成的。

Dynamic Library

  • 大部分iOS开发者的日常工作应该都是写App,估计很少有人写过dylib,因此对dylib的概念很陌生。殊不知,在Xcode工程里导入的各种framework,链接的各种lib,其实本质都是dylib。可以用“file”命令验证一下,如下:
1
2
3
file /Users/snakeninny/Code/iOSSystemBinaries/8.1.1_iPhone5/System/Library/Frameworks/UIKit.framework/UIKit

/Users/snakeninny/Code/iOSSystemBinaries/8.1.1_iPhone5/System/Library/Frameworks/UIKit.framework/UIKit: Mach-O dynamically linked shared library arm
  • 如果把焦点转移到越狱iOS中,Cydia里的各种tweak无一不是以dylib的形式工作的,正是这些tweak的存在让我们能够随意定制自己的iOS。在逆向工程中,我们会频繁接触各种dylib,因此有必要了解一些相关知识。
  • 在iOS中,lib分为staticdynamic两种,其中staticlib在编译阶段成为App可执行文件的一部分,会增加可执行文件的大小。因为App尺寸变大,启动时需要加载的内容变多,所以可能会导致App启动变慢。dylib则相对“智能”一些,它不会改变可执行文件的大小,只有当App需要用到这个dylib时,iOS才会把它加载进内存,成为App进程的一部分。
  • 值得一提的是,dylib虽然充斥在iOS的各个角落,是逆向工程的重要目标类型,但其本身并不是可执行文件,不能独立运行,只能为别的进程服务,而且它们寄生在别的进程里,成为了这个进程的一部分。因此,dylib的权限是由它寄生的那个App决定的,同一个dylib寄生在系统App和StoreApp里时的权限是不同的。例如,你写了一个Instagram的tweak,用来把喜欢的图片保存在本地,如果保存目录是/var/mobile/Containers/Data/下App对应的Documents目录,那么因为Instagram是一个StoreApp,这样的操作是没有问题的,tweak能够正常工作。而如果保存目录是/var/mobile/Documents,那么在兴高采烈地保存了一大堆美图,准备回头细细品味时,你就会发现/var/mobile/Documents里啥图片也没有——操作都被sandbox给禁掉了。

Daemon

  • 相信绝大部分开发者从接触iOS开发的第一天起,就不断被苹果灌输这样一个观念——iOS中没有真正的后台多任务,你的App在后台将被大大限制。如果你是一个纯粹的App Store开发者,坚信并坚守这个观念,那么它将是你的App通过苹果审核的助推剂;但想要在学习逆向工程的同时了解一些官方文档没有阐述的事实,那么你就要保持冷静,理性思考。让我们一起回想一下iPhone上的一些现象。

    • 当我们正在用iPhone上网或刷微博时来了一个电话,所有其他操作会立即中断,iOS第一时间将接听电话的界面呈现在我们面前。如果iOS中没有真正的后台多任务,系统是如何实时处理这个来电的呢?
    • 对于那些经常收到垃圾短信和骚扰电话的朋友来说,类似于SMSNinja这样的防火墙软件必不可少。如果它不能常驻iOS后台,怎么能够实时地处理并过滤收到的每一条短信呢?
    • Backgrounder是一款iOS 5时代的插件,它能够帮助App实现真正的后台运行。有了它,我们再也不用担心因为push功能的不给力而漏收QQ消息啦!
  • 如果iOS中没有真正的后台多任务,Backgrounder怎么会存在呢?

  • 这些现象无一不说明iOS实际上存在真正的后台多任务。那么难道是苹果说错了?并不是!对于StoreApp来说,当用户按下home键时,进程就进入后台了,大多数功能都会被暂停;也就是说,对于遵纪守法的App Store开发者来说,可以把iOS看作是没有真正后台多任务的系统,因为你唯一能干的事不支持后台多任务。但iOS源于OSX,后者又跟所有类UNIX操作系统一样,有daemon(即守护进程,Windows称Service)的概念。越狱开放了iOS全文件系统,daemon也得以展现在我们面前。
  • Daemon为后台运行而生,给用户提供了各种“守护”,如imagent保障了iMessage的正确收发,mediaserverd处理了几乎所有的音频、视频,syslogd则用于记录系统日志等。iOS中的daemon主要由一个可执行文件和一个plist文件构成。iOS的根进程是launchd,它会在开机时检查/System/Library/LaunchDaemons/Library/LaunchDaemons下所有格式符合规定的plist文件,然后启动对应的daemon。这里的plist文件与App中的Info.plist文件作用类似,即记录daemon的基本信息,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
plutil -p
/Users/snakeninny/Code/iOSSystemBinaries/
8.1.1_iPhone5/System/Library/LaunchDaemons/com.apple.imagent.plist
{
"WorkingDirectory" => "/tmp"
"Label" => "com.apple.imagent"
"JetsamProperties" => {
"JetsamMemoryLimit" => 3000
}
"EnvironmentVariables" => {
"NSRunningFromLaunchd" => "1"
}
"POSIXSpawnType" => "Interactive"
"MachServices" => {
"com.apple.hsa-authentication-server" => 1
"com.apple.imagent.embedded.auth" => 1
"com.apple.incoming-call-filter-server" => 1
}
"UserName" => "mobile"
"RunAtLoad" => 1
"ProgramArguments" => [
0 =>
"/System/Library/PrivateFrameworks/IMCore.framework/imagent.app/imagent"
]
"KeepAlive" => {
"SuccessfulExit" => 0
}
}
  • 相对于App,daemon提供的功能要底层得多,逆向难度也要大得多,随意改动造成的后果当然也就严重得多,所以白苹果的惨案才会时有发生。在iOS逆向工程初学阶段,请不要把daemon当作练习目标;当你逆向了几个App,有了一定的心得和积累后再挑战这些daemon才是比较明智的选择。相比App,逆向daemon花费的时间和精力会更多,但更多的付出一定会带来更丰厚的回报。例如,“iOS上的第一款电话录音软件”Audio Recorder就是通过逆向mediaserverd这个daemon实现的。

总结

  • 简单介绍了常见的二进制文件类型,它们都是App Store开发者不需要了解也接触不到的知识,在学习iOS逆向工程时很容易形成概念盲区。
  • 本文旨在科普那些在逆向工程中非常重要但苹果官方闭口不提的iOS系统级知识点,从而为AppStore开发者打开iOS逆向工程的这扇窗。

参考

要不要鼓励一下😘