“iOS应用安全权威指南”读书笔记
“第二部分”为安全性测试,共包括四、五、六章
第四章:构建测试平台
本章将会列出一些工具,用于检查代码和测试iOS应用。此外,还会展示如何构建一个强大便携的测试平台,其中包含设置得当的Xcode实例,交互式的网络代理、逆向工程工具和用来绕过iOS平台安全检查的工具。
本章还会介绍如何修改Xcode工程的设置选项,以便更好的确认和修正bug。此外还会介绍如何借助Xcode的静态分析和编译选项生成安全的二进制代码,以及如何进行更深层次的bug检测。
拆掉辅助轮
OS X系统有一些默认的设置能够阻止你挖掘系统的内部信息,为了获取这些隐匿的内部信息,需要在终端Terminal输入如下命令:
defaults write com.apple.Finder AppleShowAllFiles TRUE
defaults write com.apple.Finder ShowPathbar -bool true
defaults write com.apple.Finder _FXShowPosixPathInTitle -bool true
defaults write NSGlobalDomain AppleShowAllExtensions -bool true
chflags nohidden ~/Library/
①:Finder中的所有文件都变为可见,哪怕是那些以“.”开头的隐藏文件也不例外。(第一行回车后,再输入killall Finder即可生效。defaults write com.apple.Finder AppleShowAllFiles FALSE为恢复此设置)
②:底部显示详细的文件路径(defaults write com.apple.Finder ShowPathbar -bool false为恢复此设置)
③:可以在顶部看到路径完整信息(defaults write com.apple.Finder _FXShowPosixPathInTitle -bool false为恢复此设置)
④:显示所有文件的扩展名:(defaults write NSGlobalDomain AppleShowAllExtensions -bool false恢复此设置)
⑤:能看到用户相关的Library目录,iOS模拟器将所有的数据都储存在此目录下。
PS:以上的操作,终端再输入killall Finder命令(重启一下Finder)即可生效。
chflags命令将那些苹果公司认为会迷惑用户的隐藏文件全部显示了出来,例如/tmp或/usr。这样一来就无需像过去那样,每次都用命令行查看模拟器目录内容。
one more thing,把$SIMPATH添加到Finder的侧边栏,可以便于访问。使用$SIMPATH能够很方便地检查iOS模拟器的文件系统,但是在默认设置下,Finder没有$SIMPATH。想要实现上诉设置,可以在终端Terminal输入下列命令:
cd ~/Library/Application\ Support
open .
在打开的Finder窗口中,将iPhone模拟器目录拖到侧边栏上,现在已经去掉了所有的辅助轮。
推荐几个测试设备
推荐使用一台已越狱的设备和一台未越狱设备作为备用。过去的经验告诉我们,越狱设备在注册推送通知上存在问题。有了越狱设备,能更加深入的甚是整个iOS系统的结构和工作细节,还能很方便地进行黑盒测试,这些在未越狱设备上是行不通的。
使用设备测试和使用模拟器测试
苹果自带的模拟器是在x64平台上编译的。真机和模拟器的区别如下:
①大小写敏感:OS X系统运行的HFS+文件系统默认不区分大小写,而iOS默认使用区分大小写的文件系统。虽然这不会引起安全性问题,但是在修改程序时会产生互操作性(interoperability)问题。
②库:iOS模拟器的二进制连接的是OS X框架,OS X框架和iOS上的框架有些不同,从而导致两者在表现上有细微的差异。
③内存和性能:在模拟器上运行的应用程序有天然的优势,可以最大化的利用电脑开发机器的硬件资源。
④摄像头:iOS模拟器上不能使用真机设备的摄像头功能。
⑤短信和蜂窝:模拟器上不能使用。
与老版本iOS不同,新版本的iOS模拟器可以模拟钥匙串API,这意味着可以管理、储存、操作证书了,根据下面的路径可以找到这个功能背后的文件:$SIMPATH/Library/Keychains
网络和代理设置
大部分情况下,测试iOS应用的第一步是搭建一个代理,在代理上运行程序可以很方便的监听和修改设备与远端服务器之间的通信。绝大部分iOS安全测试工程师都在使用类似BurpSuite的工具。
绕过TLS验证
安全传输层协议(TLS)用于在两个通信应用程序之间提供保密性和数据完整性。
测试环境下通过代理运行程序还需要解决一个主要问题:如果无法验证服务器证书,iOS会拒绝正在进行的TLS/SSL连接,需要验证通过才允许连接。由于iOS无法验证当前设备的代理证书,凡是使用代理的测试都无法进行。
如果使用BurpSuite,那么只需简单地配置一下设备或模拟器就能得到一个CA证书(Certificate Authority),有了证书就可以正常使用Burp作为代理,在手机的Safari中访问http://burp/cert/即可完成。这种方式在真机和iOS模拟器上都能正常工作。也可以发送带CA附件的电子邮件或跳转到包含CA的网站,把CA证书安装到物理设备上。
对于iOS模拟器,有一种一劳永逸的方式,几乎适用于所有的Web代理,那就是将代理软件CA证书的指纹直接添加到iOS模拟器的信任仓库中。这个信任仓库其实是一个SQLite数据库,和典型的证书打包相比,将指纹添加到信任仓库的操作有点烦琐。笔者推荐写一个脚本来自动化此任务,如果想找个例子,可以使用Gotham Digital Service写好的Python自动化脚本。具体的脚本代码在这里:https://github.com/GDSSecurity/Add-Trusted-Certificate-to-iOS-Simulator/。
如果要使用这个脚本,则需要获取目标CA证书。首先将Firefox(笔者认为Chrome是一个更安全的浏览器,但是配置齐全的Firefox在设置代理时更加方便)配置为使用代理(127.0.0.1 端口8080针对Burp);然后尝试访问任意的SSL站点,应该会得到一个熟悉的证书警告。在导航菜单的Add Exception-View-Details中单击PortSwigger CA条目,如下图所示。
单击Export,然后按照提示操作,保存好CA证书,打开终端Terminal运行这段Python脚本,把证书添加到仓库:
python ./add_ca_to_iossim.py ~/Dowloads/PortSwiggerCA.pem
不幸的是,在编写此书时,还无法用原生的方式限制代理范围。如果想让iOS模拟器使用HTTP代理,那就必须让整个系统都使用代理,所以需要在主机系统的偏好设置上配置代理,如下图所示:
如果这台机除了测试还要做一些其他事情,那就要为其他应用程序配置单独的代理,可以使用FoxyProxy的浏览器插件来实现。
用stunnel绕过SSL
有一个绕过SSL端点的验证方法:建立一个本地终端节点,让应用程序直接使用这个本地节点。只需要修改plist文件中的端点URL即可,无需重新编译。
如果想观察纯文本形式的流量,这种方法尤为有用(例如使用Wireshark),但是网络端仍然只能通过HTTPS访问。首先,下载stunnel,它会在HTTPS要访问的远端服务器与本地机器之间扮演中间人的角色。如果通过Homebrew安装,stunnel的配置文件路径为/usr/local/etc/stunnel/stunnel.confsample。将该文件移动或复制到/usr/local/etc/stunnel/stunnel.conf,然后按如下方式编辑:
以上操作将stunnel设置成客户端模式,让它接受来自回环地址80端口的连接,然后通过SSL转发到远端服务器,编辑完成后设置Burp,这样就能将回环监听者作为代理。确保选中Support invisible proxying选项,然后按照下图设置。
①打开已下载好的Burp软件,进入之后点击Proxy节点,然后选中第一条点击Edit,填入如下图片信息即可。
设备上的证书管理
略
在设备上设置代理
略
Xcode和构建设置
在Xcode中,项目的设置选项如迷宫般复杂,几乎没有人能够精通所有设置背后的含义。这一节将仔细研究这些选项,讨论一下为什么或不需要某些选项,最后展示如何利用Xcode在真正的问题发生时找出bug。
为生活增加点挑战
首先,将警告视为错误。大部分警告都是由clang产生的,clang属于Xcode编译器的前端(foretend)(LLVM是编译器后端,具体可看iOS编译原理),值得认真对待。这样做可以减少代码复杂度、确保语法正确、还可以捕获那些难以发现的错误。
总而言之,应该启用项目构建配置中的所有警告,并将警告提高到错误的高度,强迫自己尽可能在开发的早期阶段解决掉bug。
在Build Settings里面,找到Warning Policies,Treat Warnings ad Errors修改为YES。然后再Warnings部分,开启想要的选项。
为了在“专家模式”下开发,可以在Custom Compiler Flags的Other Warning Flags添加-Wextra或-Weverything标记。-Weverything或许有些过于严格,除非你对clang的内部结构很感兴趣,可以如此标记,否则通常使用-Wextra。
下表列出了两个肯定会遇到的警告:
启用完整的ASLR
在iOS4.3开始,苹果引入了ASLR(地址空间布局随机化)(第一章有提到过)。ASLR确保了内存中的程序的结构和数据(库、主程序、栈、堆及内存映射文件)被加载到虚拟机地址空间中不可预测的位置。这个机制使得代码执行时的逆向工作更加困难,因为很多针对特定库的调用都要依赖于这些虚拟地址,还需要引用堆和栈上的数据。
这个机制非常有效,但应用必须构建一个位置独立的可执行程序(PIE(Position Independent Enable)),这就要求编译器可以生成一堆机器代码,而这堆代码的功能与其在内存中的位置无关。如果没有这个选项(ASLR),可执行程序和栈上的数据在内存中的位置将保持不变,即使是在重启之后也不会变,这就使得攻击者可以轻而易举地完成攻击。
为了确保ASLR和PIE完全启用,可在Xcode中做如下设置:
Generate Position-Dependent Executable和Generate Position-Dependent Code均设置为No,这样做告诉编译器,“不要创建位置独立的可执行文件!”
为了确保能够正常黑盒测试或者正确开启应用程序的ASLR,可以使用二进制工具otool检查,如下所示:
在MH_MAGIC每一行的结尾,如果设置都正确无误,那么将会看到高亮加粗显示的PIE标志。(注意:如果想看到结果,那么必须在真机上进行编译,不能在模拟器上编译)
Clang和静态分析
在计算机安全领域,静态分析一般指使用工具来分析代码并找出安全漏洞。作为构建工具链的一部分,clang是嵌入式静态分析语言的一个绝佳位置。
从Xcode3.2开始,Xcode就已经整合了clang的静态分析,并且为用户提供了一个带有UI界面的工具(在哪里?)。用户可以使用该工具很直观地完成逻辑追踪、代码泄漏已经通用API误用等分析操作。虽然clang的静态分析很方便,但是又一些重要的特性,Xcode是默认禁止的。其中值得关注的特性有:对C语言库函数等经典威胁的检查,比如针对strcpy和strcat的检查就是默认关闭的。你可以在Xcode的Build Setting的Static Analyzer - Issues - Security中进行打开
Address Sanitizer和动态分析
最新版的Xcode自带的clang/llvm已经包含了Address Sanitizer(ASan)功能。ASan是一个类似Valgrind的动态分析工具,但是ASan运行速度更快,覆盖率更广。ASan可以检测出堆栈移溢出和释放后又被使用的bug,还能找到关键的安全漏洞。ASan确实会对性能有影响(程序执行的速度估计会比原来慢一倍),因此不要在生产版本中启用这个选项。但是在测试、质量保证检测或是缺陷测试(fuzzing)阶段,尽情享受这个特性带来的便利吧。
启用ASan,可根据下图,在Xcode中进行配置。对于任何危险的崩溃,ASan将会在终端写入附加的调试信息。
使用Instruments监控程序
Instruments可以细粒度观察应用程序的动态,还可以监控网络套接字Socket的使用率、找出内存分配问题、监控文件系统的交互。对于那些储存在本地的应用程序对象,Instruments能轻易找出哪些部分敏感信息可能会泄漏。
激活Instruments
在Xcode中,长按Run按钮,选择Build for Profiling选项。
在Open Developer Tool中,可打开Instruments。
用Watchdog监控系统活动
略
第五章:使用lldb和其他工具进行调试
Xcode的强大功能之一就是调试iOS应用程序。lldb是Xcode的默认调试器。lldb为Objective-C而生,对多线程的支持很好,甚至可以用它来查看对象的内部结构。不过金无足赤人无完人,lldb唯一的缺点就是必须抛弃好不容易掌握的gdb知识,过渡到一个新的环境中。(有关Xcode调试,推荐iOS 7 Programming:Pushing the Limits这本书,http://iosptl.com/)
lldb有用的特性
lldb有命令行,但你也可以使用图形化用户界面来查看当前线程状态、注释汇编以及对象细节,并执行交互操作。注意:如果你已经很熟悉gdb,那么LLVM命令也不成问题,因为LLVM工程将gdb的常用命令无缝映射到了lldb,详情参见http://lldb.llvm.org/lldb-gdb.html。
下面是一些断点的设置:
(lldb)breakpoint set --name myfunction --name myotherfunction
(lldb)breakpoint set --name "[myClass methodCall:]"
(lldb)breakpoint set --selector myObjCSelector:
(lldb)breakpoint set --method myCPlusMethod
①命令可以在myfunction和myotherfunction等多个函数前设置断点
②命令可以在特定的Objective-C实例和方法处设置断点,也可以像①那样方式进行分组
③命令可以中断特定的selector/method调用,只要使用--selector,无论这些方法由什么类实现,它都会中断对应方法名的所有调用。
④命令可以中断特定的C++方法,只需要在定义断点时用--method替代--name即可
如:(lldb)breakpoint set --name main来在main函数打上断点;breakpoint可以缩写为break。
需要创建多个断点时,可以在命令行中使用-r标志来中断满足正则表达式的函数。类似这样:(lldb)break set -r tableVeiw (lldb)break list
启用和禁用位置与操作普通断点一样,只需要使用break、disable和break enable加上数字标识符即可。
查看帧和变量
与gdb类似,你可以使用bt命令(backtrace的缩写)查看当前线程的调用堆栈。正常情况下,你可以使用up、down和frame select命令在这些帧之间进行导航。然后在某些Xcode中存在一个bug,即通过命令行方式导航到某个帧会立即跳转回Debug导航器中被选中的帧位置。这种情况下,必须使用调试导航器手动在这些帧之间进行切换。
为了查看当前帧变量,你可以使用frame variable命令。这个命令会显示本地栈帧的变量名和参数,以及他们的类型和内存地址。你也可以使用图形化调试其中的上下文菜单来打印或编辑变量的内容。
使用frame select 命令可以接收一个数字参数,表示想要查看的栈帧,如果你想查看调用栈上层的内容可以使用这种方式。
注意,这种方式适用于非工程代码,例如CocoaAPI,它的源代码通常不可见,隐藏lldb会显示对应的汇编指令。(如果想进一步了解iOS和ARM上的汇编,请看Ray Wenderlich的教程http://www.raywenderlich.com/37181/ios-assembly-tutorial/)(中文版翻译:https://www.jianshu.com/p/544464a5e630)
你也可以使用lldb的po(print object的缩写)命令来查看对象的值
可视化查看对象
略
操作变量和属性
expr命令,如(lldb)expr (void)[self.mytextVeiw setText:@"Bar"]。因为lldb不清楚这种方法调用的返回值类型,你必须使用expr命令指定一个空类型(void)。类似的,如果你调用的方法返回一个int类型,必须进行显示类型转换。对于简单的赋值如myInt=66,只需输入expr和赋值即可:(lldb)expr myInt=66
断点行为
这个好像没啥可说的,断点可以设置在满足某些条件才触发,也可以设置出发时候打印日志等。
使用lldb进行安全分析
错误注入
假如你有一个应用,使用自定义的二进制网络协议在客户端与远程服务器之间传递数据。使用现成的代理很难拦截和修改数据(因为无法解析传输的数据),因此很难测试错误数据是否会导致程序崩溃。另外,如果能够修改数据,就可以让之后的测试更加简单。
假如数据可以修改,你可能会想到用一个特殊的密钥替换掉随机生成的密钥。请看图片中代码所示。这段代码会用你选择的密钥来加密数据,这样你拿到的结果就可以解密出来,它们再也不是一大团看不懂的二进制数据了。下面的代码会在应用程序把加密密钥储存到钥匙串之前修改它,这样就能在之后的通信中使用我们选择的密钥了。
①位置的代码打印了当前frame的变量,注意发送给addToKeychain:forService:选择器的参数。这个例子中我们感兴趣的是key,它被储存在item参数中,然后添加到字典中。②和③是这些key对应的值。接着使用expr命令④修改钥匙串字典。命令⑤验证了当前的key值已经是我们修改后的新值。
数据追踪
如果应用使用主密码进行加密,那么它通常会在加密前对数据进行检查。这些数据的加密方式可能比较复杂。如下代码:
如果用方法选择器在方法encrypt②上中断,则可以使用frame variable命令①来查看本地变量。可以看到输出中有data和encData,本例中我们只对前者③有兴趣,因为这个数据会被加密然后返回。还可以用这种方式在数据被编码传输之前进行修改。
查看核心框架
lldb可以用来挖掘苹果的私有API。如果你想深入研究某个API的行为,推荐你使用lldb。举个栗子,加入我们正在研究NSCache,
这里,假设我们调用removeAllCacheResponse方法①,当前磁盘使用率依然为98304bytes②。看来清楚缓存是无效的。别慌,你将在第九章看到这个问题的解决方案。不过可以先自己考虑下它内部的运行机制,这将帮助你更好的搞清楚iOS平台的工作机制,更深入了解应用程序的行为模式。