iOS 逆向指南:界面分析

写几篇文章总结一下 iOS 逆向的整个流程,逆向初学者可以作为入门指南。内容包括逆向工具和环境配置、踩坑点、界面分析、砸壳、静态分析、动态分析、lldb 调试、推荐 hopper 和 IDA 插件、IDA 插件的编写、各种分析技巧。

逆向的作用

对于 iOS 开发工程师:

  • 通过查看界面、阅读汇编代码,研究参考其他 app 的实现方案
  • 研究系统库的实现逻辑,用于修复 bug
  • 查找使用私有 API,实现一些黑科技功能
  • 接触底层原理,对 iOS 安全防护有更深入的掌握
  • 获取其他 app 中的资源,例如游戏贴图
  • 对第三方 app 进行一些自定义"改造"

我个人觉得逆向对我最大的帮助就是能够查看各种闭源代码的实现,满足好奇心的同时也拓展了思路,能够学到很多。

工具

逆向中常用的工具有:

  • 越狱 iOS 设备一台(越狱后需要安装 openSSH 和一系列开发工具,建议使用 32 位的 armv7、armv7s 设备,因为旧版 Hopper Disassembler 不支持为 arm64 架构的汇编生成伪代码,最新版虽然支持,但是仍然不如 32 位的易读)
  • reveal:查看任意 app 界面
  • dumpdecrypted:对 app store 上下载的 app 进行砸壳解密的工具
  • clutch:和 dumpdecrypted 功能相同,也是 app 砸壳工具,用法更简单
  • class-dump, class-dump-z:分析 Mach-O 文件的类,导出 Objective-C 头文件
  • Hopper Disassembler:反编译 iOS app 工具
  • IDA:更强大的反编译工具, 缺点是价格贵

通过这些工具的作用,也差不多能一窥 iOS 逆向的面貌。

界面分析

这篇文章首先讲讲对普通开发者最有用的界面分析:如何查看任意 app 的界面结构。

有时候我们对某个 app 的界面实现方式感兴趣,直接查看它的界面层级就能明白大致的思路。

使用 reveal 查看 app 界面

Reveal 是一个 Mac 端的商业软件,可以查看任意 app 的界面构成,以及所属的类名、约束等信息。找到对应的功能的类名后,就可以使用后面的工具找对应的头文件查看方法。Reveal 有试用版,试用期30天,官方和淘宝店铺有正版合作,有国区优惠。使用方法如下。

1.客户端安装reveal插件

Reveal 的环境配置有一些坑,并且在不同的系统版本上配置方式不一样。

配置

iPhone 越狱后在 Cydia 里搜索RevealLoader安装插件。Reveal 有多个版本,reveal 1.6.3支持 iOS 7,reveal 2(版本号从2一直增加到了最近的13)最低支持 iOS 8。如果用的是 reveal 2,则在 iOS 9 及以下系统仍然使用RevealLoader,iOS 10 越狱机上则安装Reveal2Loader。安装后在设置里会出现 reveal 工具栏(可能需要重启设备),找到需要查看的 app,打开开关,然后重启 app。

如果使用 reveal 2,还需要手动添加一个动态库到设备上,原因是客户端的库需要和 Mac 端的库版本一致。在 Mac 端的 reveal 中打开 Help -> Show Reveal Library in Finder (根据目标机器的类型选择 iOS Library 或者 TvOS Library),将RevealServer.framework拷贝出来。

如果是 iOS 9,使用RevealLoader插件时,需要把RevealServer.framework/RevealServer二进制文件重命名为libReveal.dylib,拷贝到设备的/Library/RHRevealLoader文件夹下,没有文件夹则手动创建一个。

如果是 iOS 10,使用Reveal2Loader插件时,则将RevealServer.framework拷贝到目标设备上的/Library/Frameworks/目录下,如果已经存在同名文件,则替换。

最后,终端 SSH 到 iOS 设备,执行killall SpringBoard,重启 SpringBoard 即可。

reveal1.jpg
原理

这个工具是把 reveal 的库添加到了/Library/MobileSubstrate/DynamicLibraries,并且提供了一个管理界面,并不是必须安装。当 app 启动时,MobileSubstrate 会自动把 reveal 的动态库注入到 app 中,然后和 Mac 端的软件进行通信。

如果选择不安装此工具,配置方法请参考https://zhuanlan.zhihu.com/p/19646016?columnSlug=iOSRe

2.Mac 端打开 reveal 软件

Mac 端安装 reveal 软件,把 iOS 设备和 Mac 连接到同一 wifi 下。
开启需要查看的 app,进入需要查看的界面,在 Mac 端 reveal 里选中,即可开启。

3.查看页面信息

你可以拖动界面,立体地查看界面结构。这个查看方式是 reveal 首先发布的,后来苹果官方把它集成到了 Xcode 中。

页面的右边,可以找到此页面对应的类名,以及尺寸、约束等信息。
下面是微信的扫一扫界面,可以看到界面的类名为 CameraScannerView 和 CameraScannerViewController。reveal2 开始,也可以查看 extension 的界面,比如通知中心插件。

reveal查看界面

其他界面查看方式

不用 reveal 也可以查看 app 的界面。

1. lldb 打印界面层级

可以在动态分析 app 时,用 lldb 打印出 app 的界面层级。命令是:

po [[UIApp keyWindow] recursiveDescription]

打印出来的结果类似于:

<UIWindow: 0x7fbb91511d50; frame = (0 0; 375 667); gestureRecognizers = <NSArray: 0x60400025bc90>; layer = <UIWindowLayer: 0x604000235820>>
   | <UILayoutContainerView: 0x7fbb91513e00; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x604000235fe0>>
   |    | <UIView: 0x7fbb9151a440; frame = (0 0; 375 0); layer = <CALayer: 0x604000237420>>
   |    | <UILayoutContainerView: 0x7fbb91514420; frame = (0 0; 375 667); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x60400025d9d0>; layer = <CALayer: 0x604000235e60>>
   |    |    | <UINavigationTransitionView: 0x7fbb91516070; frame = (0 0; 375 667); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x604000236920>>
   |    |    |    | <UIViewControllerWrapperView: 0x7fbb9151aa20; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x6040000379e0>>
   |    |    |    |    | <UIView: 0x7fbb9160d7b0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x60400023e4c0>>
   |    |    |    |    |    | <UIButton: 0x7fbb917190d0; frame = (170 90; 34 30); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x60000022bf20>>
   |    |    |    |    |    |    | <UIButtonLabel: 0x7fbb91611210; frame = (0 6; 34 18); text = 'push'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x604000292fc0>>
   |    |    |    |    |    | <UIButton: 0x7fbb91606c30; frame = (123 207; 128 30); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x60400023a820>>
   |    |    |    |    |    |    | <UIButtonLabel: 0x7fbb91418310; frame = (0 6; 128 18); text = 'push and auto pop'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600000299cd0>>
   |    |    |    |    |    | <_UILayoutGuide: 0x7fbb9160db40; frame = (0 0; 0 64); hidden = YES; layer = <CALayer: 0x60400023e920>>
   |    |    |    |    |    | <_UILayoutGuide: 0x7fbb9160df50; frame = (0 667; 0 0); hidden = YES; layer = <CALayer: 0x60400023e700>>
   |    |    | <UINavigationBar: 0x7fbb9150b170; frame = (0 20; 375 44); opaque = NO; autoresize = W; layer = <CALayer: 0x604000039660>>
   |    |    |    | <_UIBarBackground: 0x7fbb9150e540; frame = (0 -20; 375 64); userInteractionEnabled = NO; layer = <CALayer: 0x6040000395e0>>
   |    |    |    |    | <UIImageView: 0x7fbb9150e9d0; frame = (0 64; 375 0.5); userInteractionEnabled = NO; layer = <CALayer: 0x604000039500>>
   |    |    |    |    | <UIVisualEffectView: 0x7fbb9150ec00; frame = (0 0; 375 64); layer = <CALayer: 0x6040000394e0>>
   |    |    |    |    |    | <_UIVisualEffectBackdropView: 0x7fbb91710df0; frame = (0 0; 375 64); autoresize = W+H; userInteractionEnabled = NO; layer = <UICABackdropLayer: 0x600000228da0>>
   |    |    |    |    |    | <_UIVisualEffectSubview: 0x7fbb917119a0; frame = (0 0; 375 64); autoresize = W+H; userInteractionEnabled = NO; layer = <CALayer: 0x600000228f40>>
   |    |    |    | <_UINavigationBarLargeTitleView: 0x7fbb91511290; frame = (0 0; 0 44); clipsToBounds = YES; alpha = 0; hidden = YES; layer = <CALayer: 0x604000039720>>
   |    |    |    |    | <UILabel: 0x7fbb9171bae0; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x6000002972f0>>
   |    |    |    | <_UINavigationBarContentView: 0x7fbb9150f560; frame = (0 0; 375 44); clipsToBounds = YES; layer = <CALayer: 0x604000039400>>
   |    |    |    |    | <_UIButtonBarButton: 0x7fbb9171c570; frame = (0 0; 80 44); layer = <CALayer: 0x60400023d6c0>>
   |    |    |    |    |    | <_UIModernBarButton: 0x7fbb915178e0; frame = (8 11.5; 13 21); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x60400023f200>>
   |    |    |    |    |    |    | <UIImageView: 0x7fbb9141caf0; frame = (0 0; 13 21); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x600000230300>>
   |    |    |    |    |    | <_UIBackButtonContainerView: 0x7fbb91419ef0; frame = (0 0; 80 44); autoresize = W+H; userInteractionEnabled = NO; layer = <CALayer: 0x60000022ec20>>
   |    |    |    |    |    |    | <_UIModernBarButton: 0x7fbb9150c480; frame = (27 9; 53 23.5); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x60400023e900>>
   |    |    |    |    |    |    |    | <UIButtonLabel: 0x7fbb91511700; frame = (-1.5 3; 53 20.5); text = 'Master'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x604000291b70>>
   |    |    |    |    | <UILabel: 0x7fbb9171c290; frame = (148.5 12; 78.5 20.5); text = 'Test Push'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600000297ca0>>
   |    |    |    | <_UINavigationBarModernPromptView: 0x7fbb9170cf10; frame = (0 0; 0 44); alpha = 0; hidden = YES; layer = <CALayer: 0x6000002270c0>>
   |    |    |    |    | <UILabel: 0x7fbb9170d430; frame = (0 25.5; 0 0); text = ''; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600000290400>>

recursiveDescriptionUIView的私有API,注意不是所有的UIView都能使用,在有些视图上会crash。

可以再用nextResponder获取UIView对应的 view controller。

由于看得眼花,所以这个方式我很少用。

2. 直接使用 Xcode 的Debug View Hierarchy

可以用 Xcode 直接调试重签名后的 app,然后就可以用Debug View Hierarchy查看界面层级了。

参考:iOS逆向:用Xcode直接调试第三方app

3. 越狱插件:FLEX

可以安装越狱插件,直接注入到 app 中,打印出界面信息。可以去 Cydia 中直接下载安装,也可以去这里编译源码使用:FLEX

总结

界面分析对于猜想某个界面的实现方式有很大帮助。想要分析其它功能时,也可以通过界面分析先找到对应的类,再进一步进行静态分析和动态分析。下一篇文章将会讲解如何进行静态分析。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容

  • 上一篇文章:"探秘工具:MacOS工具探索"[https://www.jianshu.com/p/e20d7142...
    木子心语阅读 2,654评论 1 1
  • 序 转眼就到了2016年的最后一天,还记得12月月初的时候组长说,元旦有安排了吗?没安排的话值班吧。我当时就愣住了...
    心彻阅读 327评论 4 3
  • 今天先生的姨妈家新添了一位小孙女,于是便和先生一起驱车前往。也全程陪伴了一下王好好。 有了小伙伴,王好好小朋友就非...
    慢节拍阅读 72评论 0 0
  • 早晨,我翻了个身,习惯性的伸手摸了一下身边,空的。猛然惊醒,发现大床空荡荡的,没有爸爸,没有妈妈。我跳下床,把窗帘...
    丫丫与亚亚阅读 567评论 1 3
  • 俗套的爱情故事里,总是闪耀着现实的希望之光。《西雅图夜未眠》就是非常俗气而经典的爱情故事,之前汤唯主演的《北京遇上...
    银河弦歌阅读 175评论 0 4