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

總結

界面分析對於猜想某個界面的實現方式有很大幫助。想要分析其它功能時,也可以通過界面分析先找到對應的類,再進一步進行靜態分析和動態分析。下一篇文章將會講解如何進行靜態分析。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章