iOS逆向指南:静态分析

静态分析是指对二进制包进行反编译,分析静态的代码逻辑,从而找到关键的代码所在。找到关键代码也就基本实现了逆向的目的,能够经过修改二进制对关键代码作出自定义修改,达到破解的目的。python

本文内容包括:app 砸壳过程、工具和环境的坑、导出 OC 头文件、使用 hopper 和 IDA 反编译、arm 寄存器功能、静态分析经验、推荐的 IDA 插件、如何分析系统库。ios

对 app 砸壳解密

从 App Store 下载的 app 是通过加密的,须要对其进行解密后,才能进行分析。若是你懒得砸壳,能够直接去各类苹果助手下载越狱版 app,那些是已经解密过的。可是若是要找的 app 在助手上没有,就只能本身砸壳了。git

砸壳可使用 dumpdecrypted,也可使用更简单的 clutch。这里用 dumpdecrypted 讲解。步骤以下。github

1.下载 dumpdecrypted

github.com/AloneMonkey…下载源码,编译出一个 dumpdecrypted.dylib 文件。这个版本的 dumpdecrypted 添加了对 framework 的 dump。算法

2.安装 openSSH

iOS 9及如下系统,在 Cydia 里安装 openSSH 便可。sql

iOS 10越狱自带了 openSSH,可是默认是关闭的,须要作一点修改。swift

若是是用的 yalu 越狱:windows

  • 1.用苹果助手或者其余工具进入 iOS 的/private/var/containers/Bundle/Application/yalu102/yalu102.app/
  • 2.用文本编辑器打开 dropbear.plist 文件。
  • 3.替换 127.0.0.1:22 为 22。
  • 4.重启设备,从新使用越狱工具恢复越狱。

参考:bbs.iosre.com/t/make-pack…sass

或者直接去 Cydia 里安装 dropbear 插件。微信

3.链接到 iOS 设备

iOS 设备安装了 openSSH 后,在 Mac 端打开终端,确保 Mac 和 iOS 设备链接到同一网络,在终端里输入命令:ssh root@iOSIP。iOS 设备的 ip 地址:

iOS IP

在终端中输入命令:ssh root@10.5.53.182,回车,接着输入 ssh 的默认密码alpine后便可链接到 iOS 设备。

4.找到须要砸壳的 app

找到 app 所在目录,格式为/var/mobile/Containers/Data/Application/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/,可使用同步助手、itools 等工具查找。

也能够在 Cydia 里安装 ps 命令行工具后,使用ps –e命令查找,方法是 ssh 成功后,关闭全部 app,打开须要砸壳的 app,输入ps –e命令,便可打印出全部进程,/var/mobile开头的那个目录就是 app 所在的目录。

dump path

5.进行砸壳

下面的砸壳是旧版 dumpdecrypted 的方法,比较繁琐。AloneMonkey 的 这个 github.com/AloneMonkey… 更加简单。

  • dumpdecrypted.dylib拷贝到/usr/lib。 iOS 9以前是拷贝到 app 的 Document 目录的, iOS 9 以后出现了权限问题,因此拷贝到/usr/lib
  • 修改 user 为mobilesu mobile
  • 进入到某个具备写权限的目录,例如cd /var/mobile/Documents
  • 使用DYLD_INSERT_LIBRARIES加载动态库到 app 上,格式为DYLD_INSERT_LIBRARIES='dumpdecrypted.dylib的目录' '须要砸壳的app执行文件的目录',例如:DYLD_INSERT_LIBRARIES=/usr/lib/dumpdecrypted.dylib /var/mobile/Applications/F7753B03-3F06-4524-A735-5BF5B398C730/WeChat.app/WeChat。这是系统的 dyld 提供的加载动态库的功能,能够在 dyld 源代码中看到这部分逻辑。

若是出现dyld: could not load inserted library 'dumpdecrypted.dylib' because no suitable image found. Did find: dumpdecrypted.dylib: required code signature missing for 'dumpdecrypted.dylib' ,须要对 dumpdecrypted.dylib 进行签名。

在 Mac 上列出证书:security find-identity -v -p codesigning,用列出的证书签名: codesign --force --verify --verbose --sign "iPhone Developer: xxx xxxx (xxxxxxxxxx)" dumpdecrypted.dylib。把签名后的dumpdecrypted.dylib从新拷到 iOS 设备上,从新进行砸壳。

砸壳完毕后,在当前目录或者 app 的Documents沙盒目录会生成一个.decrypted后缀的文件,这就是砸壳后的文件,将其拷贝到 Mac 上便可导入其头文件、用反编译工具打开分析。能够在 Mac 上使用 scp 命令拷贝越狱机上的文件:scp -P 端口号(默认22) root@iOSIP:/var/mobile/Documents/xxx.decrypted ~/Documents/xxx.decrypted。若是拷贝的是文件夹,加上-r参数。

dumpdecrypted原理是 app 启动后会被系统解密,所以能够把解密后的内存 dump 出来。可是若是要对 app extension 进行砸壳,因为 extension 是依赖于主 app 的,不能独立启动,因此砸壳方法就失效了。能够参考这个改进版对 extension 砸壳的方法:github.com/CarinaTT/du…

使用 class-dump 导出 app 的头文件

Class-dump 是一个能够导出 Objective-C 头文件的工具,官网:stevenygard.com

经过分析头文件里的 API,能够简单地分析一个类的实现,或者查找一些私有 API。

class-dump 官网上的版本不能导出用 swift 编写的工程的头文件,当出现Error: Cannot find offset for address 0x3a546a04 in dataOffsetForAddress:这样的错误时,就说明这个 app 多是用 swift 编写的。

建议去 github 上手动编译最新版的 class-dump,或者使用 class-dump-z 代替,下载地址:code.google.com/archive/p/n…

把下载到的class-dump-z执行文件放到/usr/local/bin/,赋予执行权限chmod +x /usr/local/bin/class-dump-z。这样就能够在终端使用 class-dump 命令了:class-dump-z –H '须要导出头文件的app目录' –o '导出头文件的存放目录'

例如要 dump 系统自带的计算器,导出它的头文件,命令以下: class-dump-z -H /Applications/Calculator.app -o ~/Documents/headers

拿到砸壳后的 .decrypted 文件后,直接使用class-dump-z便可导出头文件。

此时,使用以前 reveal 定位到的类名,便可找到对应的文件,查看类里面的方法。

class-dump

能够看到,在扫一扫界面,微信使用了- (void)captureOutput: didOutputSampleBuffer: fromConnection:这个方法,说明它是截取了视频流的帧图像,再对图像进行二维码分析,而不是用AVFoundiation提供的二维码识别方法。

若是还想进一步查看方法的逻辑,可使用Hopper Disassembler对 .decrypted 文件进行反编译。

使用使用 Hopper Disassembler 静态分析

一个专门反编译 OC 程序的工具。官网:www.hopperapp.com。试用版有功能限制,30分钟退出一次,不能保存和导入反编译后的文件,不能动态调试等。

打开 Hopper Disassembler,直接将 .decrypted 文件拖入,选择对应的 CPU 架构类型便可,例如这个.decrypted 是从 iPad mini2 上生成的,那么就是 arm64。

打开后会自动进行分析,列出方法名、字符串等信息,可是大多数都是汇编语言。阅读汇编语言,还须要了解对应架构寄存器功能的知识。

在左侧能够搜索类名,方法名。

hopper1

右侧的 is referenced by 和 have reference to 能够看到方法之间的的交叉引用关系:

hopper2

按空格键能够弹出方法的逻辑跳转图:

hopper3

Hopper Disassembler 能够将汇编语言转换为 OC 风格的伪代码,可是旧版的 hopper 不能对 arm64 文件使用这个功能。建议使用 armv7s 如下的 iOS 设备的缘由就在这里。如下是使用 iPad2 越狱设备反编译后,生成的汇编代码和对应的伪代码,因为微信的代码比较复杂,这里选用的是另一个更简单的二维码 app 的代码:

hopper4

hopper5

能够看到aptureOutput: didOutputSampleBuffer: fromConnection:里,首先用取到的帧生成了一张图片,再用createRotatedImage:degrees:对图片作了一次处理,最后用decodeImage:cgimg:对图片进行二维码分析。要想查看这些方法,只须要再搜索对应的方法名就能够了。最新版 hopper 也能够双击直接跳转。

另一个反编译工具 IDA 也能够反编译 armv7 的 app ,使用方法相似,能够和 Hopper Disassembler 对照着看。须要注意的是 IDA 的 Pro 版才支持 arm64 的 app,而 Pro 版不支持免费试用。

静态分析经验总结

追踪调用流程

  • 对于静态函数,直接用交叉引用功能is referenced by查看函数在哪里被引用。注意 hopper 面板里列出的引用不是完整的,能够用快捷键x列出完整的引用
  • 对于 OC 方法,因为 runtime 在调用时不是直接引用方法,而是引用了 selector,因此须要搜索方法名字符串和 selector,而后再用is referenced by查找哪些地址引用了此字符串或者 selector,来查找方法调用
  • 经过寄存器的赋值操做回溯参数的传递
  • 经过查找某些关键字符串,回溯到关键函数

注意,反汇编工具备时候会分析出错误的指令,因此有些函数体是丢失的,须要在反编译时手动 undefined。

分析汇编代码

  • 使用 hopper 的伪代码转换功能,能够将 OC 方法的汇编代码转换为 OC 风格的伪代码。此功能对 arm64 的支持不是很好,建议使用 armv7 或者 armv7s 的越狱机
  • 在函数的开始,32 位 arm 上前四个参数存放在 r0-r3 中,其余参数存放在栈中,结束后,返回值放在 r0 中;在 arm64 上,前7个参数存放在 x0–x7 中,返回值存在 x0 中
  • 有些代码是被开发者故意混效过的,例如打乱执行流程、加入冗余代码,能够借助一些 IDA 插件处理后再分析,例如 CrowdDetox、optimice python plugin,不过只是分析 iOS 的话,不多会遇到这种状况

基本的汇编知识

你并不须要花时间理解每一条汇编指令,只须要梳理出关键点就能理清代码的逻辑。

逆向中关键的指令:

  • ldrmov,读取指令,从地址读取数据到寄存器。
  • str,保存指令,保存数据到寄存器。
  • b,跳转指令,跳转到某个地址。
  • cmp,比较指令,说明这里有分支。

32 位 arm 的调用约定:

寄存器 描述
r0-r3 传递参数与返回值。若是断点在 OC 方法的第一行,那 r0 就是 self,r1 就是 cmd。若是超过四个参数,或者一些例如结构体的参数超过了32位 bit,那么参数将会经过栈来传递;返回值通常都在 r0 上
r4-r6, r8, r10-r11 没有特殊规定,通用寄存器
r7 栈帧指针寄存器(Frame Pointer),指向前一个保存的栈帧(stack frame)和连接寄存器(link register, lr)在栈上的地址
r9 操做系统保留
r12 IP 寄存器(intra-procedure scratch)
r13 SP 寄存器(stack pointer),是栈顶指针
r14 LR 寄存器(link register),存放函数返回后须要继续执行的指令地址
r15 PC 寄存器(program counter),指向当前指令地址
CPSR 当前程序状态寄存器(Current Program State Register),在用户状态下存放像 condition 标志中断禁用等标志

arm64 的调用约定:

arm64有 r0 - r30 是31个通用整形寄存器,PC 不能再做为寄存器直接访问。每一个寄存器能够存取一个64位大小的数。 当使用 x0 - x30 访问时,它就是一个64位的数。当使用 w0 - w30 访问时,访问的是这些寄存器的低32位。

寄存器 描述
x0–x7 传递参数与返回值。若是参数个数超过了8个,多余的参数会存在栈上;返回值通常都在 x0 上
x29 栈帧指针寄存器(Frame Pointer),指向前一个保存的栈帧(stack frame)和连接寄存器(link register, lr)在栈上的地址
x31 SP 寄存器(stack pointer),是栈顶指针;根据不一样指令,也有多是 zero register
x30 LR 寄存器(link register),存放函数的返回地址
CPSR 当前程序状态寄存器(Current Program State Register),在用户状态下存放像 condition 标志中断禁用等标志

x86-64 的调用约定:

x86-64 有16个64位寄存器,分别是:

rax,rbx,rcx,rdx,esi,edi,rbp,rsp,r8,r9,r10,r11,r12,r13,r14,r15

寄存器 描述
rax 做为函数返回值使用
rsp 栈指针寄存器,指向栈顶
rdi,rsi,rdx,rcx,r8,r9 依次用做函数参数;若是断点在 OC 方法的第一行,那 rdi 就是 self,rsi 就是 cmd
rbx,rbp,r10,r11,r12,r13,r14,r15 通用寄存器

栈帧相关的知识,能够参考:iOS开发同窗的arm64汇编入门

汇编指令速查插件

有许多颇有用的插件能够对静态分析提供帮助。

有时候看到不了解的汇编指令,每次都去 Google 查找,是一件很低效的事。能够安装插件,直接在 hopper 和 IDA 中显示指令的功能。

Hopper 插件:hopperref

Hopper 可使用 Python 编写的扩展插件。安装插件hopperref,把Show Instruction Reference.py``arm.sql``x86-64.sql拷贝到~/Library/Application Support/Hopper/Scripts/目录下便可。以后就能在 hopper 界面的菜单栏Scripts中找到Show Instruction Reference选项,点击便可输出选中指令的详细文档。

mov指令的文档:

hopperref

IDA 插件:idaref

hopperref 插件是源自 一个 IDA 的插件 idaref

idaref.py拷贝到your_ida_path/ida.app/Contents/MacOS/plugins/下,把archs文件夹拷贝到your_ida_path/ida.app/Contents/MacOS/plugins/archsarchs文件夹里是汇编指令的文档x86-64.sql``x86-64_old.sql``arm.sql``mips32.sql``xtensa.sql

以后打开 IDA,就能够在Edit菜单中多出了idaref选项,选择Start Idaref就开启了自动提示,

idaref

当选中汇编指令时,对应的文档就会显示在Instruction Reference窗口中。

idaref output

IDA 插件:FRIEND

除了 idaref,还有另外一个插件 FRIEND 也提供了汇编指令和寄存器的文档功能。只要把鼠标停在指令或者寄存器上就会显示文档悬浮窗。

须要注意的是,编译出来的 IDA dylib 插件是对应 IDA 版本的,若是要使用不一样版本的 IDA,就须要从新编译。把对应版本的FRIEND.dylibFRIEND64.dylib拷贝到your_ida_path/ida.app/Contents/MacOS/plugins/下,再打开 IDA 就会在Edit->Plugins中多出FRIEND选项。

ida FRIEND

点击选项,打开 FRIEND 的设置。须要加载 FRIEND 提供的 XML 配置文件,对应二进制文件的 x86_64 或者 arm 平台。例如x86_64.xml配置中提供了x86_64 instructions项,选中后,勾上下面的四个功能选项,点击 OK 保存。

ida FRIEND settings

以后,当鼠标停在指令或者寄存器上就会显示文档悬浮窗。

ida FRIEND instructions

识别库函数

不少时候,二进制文件中的函数都被去掉了符号,所以只能看到不少sub_100017D90这样的函数,难以直观分析。而程序会使用到不少第三方库,例如加密库、压缩库、网络库,这些第三方库通常都是开源的,能够获得函数符号,若是能恢复这部分函数的符号,就能避免浪费时间在分析这些开源代码上,也能经过分析开源库的交叉引用,追踪程序自身的逻辑。

这部分代码通常都是 C 和 C++ 函数,OC 方法的名字都保存在 Mach-O 文件的符号表中,不会被去除符号。若是你须要分析 C++ 程序,可使用下面的工具进行辅助。

FLIRT:库快速识别和鉴定技术

IDA 提供了FLIRT Signature功能,FLIRT 全称是库快速识别和鉴定技术,能够为带有符号的库文件中的函数生成签名,再把签名文件导入到分析后的 app 中,就会识别出匹配到的函数,重命名为正确的符号。

可是生成正确的签名并不容易。用于生成签名的库文件,编译时的编译器版本、配置和 app 中用到的库的编译器版本、配置须要相同。这样才能生成相同的代码,从而生成相同的代码签名。

具体的使用方法,能够在书籍IDA Pro 权威指南中找到。

识别加密函数

相似的,有些 IDA 插件能够识别程序中用到的加密常数、加密方法和压缩方法。例如 Find Crypt 能够寻找经常使用加密算法中的常数,IDA signsrch 能够寻找二进制文件所使用的加密、压缩算法,IDA scope 能够自动识别 windows 函数和压缩、加密算法。

能够从这些关键函数入手,寻找程序中的关键逻辑。

如何分析系统库

有时候在分析某个 crash 时,或者对某个系统功能感兴趣时,会须要分析特定版本的 iOS 系统库的实现,例如UIKit.framework Foundiation.framework

绝大部分时候,只须要分析模拟器版本的系统库就能够了。由于模拟器的系统库保留了全部的符号,查找交叉引用更直接。

不过有些系统库只在真机上才有,或者你须要特定版本的库用于分析 crash 时,能够在iOS-System-Symbols下载对应的系统库。

真机的系统库和模拟器的有些差异。系统库在真机上通过了不少编译优化,去除了大部分私有的函数符号,交叉引用也不像模拟器版本的那样直接。真机上的全部系统 framework 都被整合成了一个大文件,名为dyld_shared_cache_arm64或者dyld_shared_cache_armv7。函数在寻址时,是基于整个dyld_shared_cache_xxx文件进行寻址的。

当你把真机链接到 Xcode,Xcode 会把真机上的系统库拷贝到~/Library/Developer/Xcode/iOS DeviceSupport,从dyld_shared_cache_xxx中切分出每一个单独的 framework。可是当你反编译这些 framework 时,会发现代码里会使用不少无效地址的函数指针,难以分析。这是由于在dyld_shared_cache_xxx中,一个 framework 引用另外一个 framework 中的函数时,是至关于在一个库中直接引用的,直接跳转到对应的地址,而不是再用函数符号通过 lazy binder 进行调用。当 framework 从dyld_shared_cache_xxx中切分出来后,这些函数调用的地址就会指向 framework 外,没法追踪。

因此在分析真机的系统库时,最好是配合模拟器版本的系统库辅助分析,能够看到私有的符号,也能够看到更明确的交叉引用。或者用 IDA 直接分析整个 dyld_shared_cache_xxx文件,不过这样作须要反汇编整个文件,耗时很大。

如何分析静态库

静态库是由 .o 文件组成的,拖到 hopper 里只能逐个查看 .o。能够按下列步骤把 .o 整合成一个文件。

  1. 先用lipo 静态库文件 -thin arm64 -output libfile导出想要分析的架构
  2. 新建文件夹,用ar -x libfile导出全部的 object 文件
  3. 能够用grep "符号名" -rn ./在全部 object 文件中搜索符号
  4. otool -l libobject.o | grep bitcode检查是否有 bitcode
  5. ld -r -arch arm64 -syslibroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk ./*.o -o ../outputlibfile把全部 .o 文件整合成一个文件,若是有 bitcode,则须要加上-bitcode_bundle

结尾

静态分析的整个流程如上,剩下的就是积累经验了。经过静态分析查看一些简单函数的实现,在大部分状况下都足够了。不过静态分析的信息是有限的,有时候很难找到想要的函数,这时候就须要动态分析上场了。下一篇文章将讲解动态分析。

参考

相关文章
相关标签/搜索