iOS App 的逆向工程: Hacking on Lyft
About the Speaker: Conrad Kramer
我是 Conrad Kramer,工作在 Workflow 的一枚 iOS 工程師,今天要跟大家聊聊 iOS Apps 逆向工程那些事兒。通常來說,逆向工程就是試圖只通過最終產物來了解它背後工作原理的過程。在 iOS app 的世界裡,這意味著你在沒有原始碼的前提下,只通過從 App Store 下載的 .app 來發現你想要的資訊。
在做逆向工程的時候,會遇到兩個痛點:
- 使用的工具 通常很難用,因為這些工具本身就缺乏文件,而且很不出名。他們總是崩潰出錯,而且沒人去修復這些錯誤。另外,花時間找到他們以及用好他們都很難。
- 一旦你開始用這些工具了,清楚自己的目標也很重要,因為有太多事情可以幹了。此外,你還能用工具去逆向蘋果自己的框架,儘管他是閉源的。我們可以用這些技術來探索 UIKit 的某個 bug,或者某個 app 裡的 bug。
所以逆向的第一步是像自己發問:“為什麼這個 bug 會發生?他們在 UI 裡用了哪些元件?” 比如,如果你很好奇一個 app 裡是否用了 collection view 或者 table view”,你其實很容易回答這個問題,看一眼就好了。但是如果你問:“他們的 REST API 是什麼結構的?”,這個時候逆向工程就能幫你一探究竟。
我決定通過一個專案來開始這個主題,目標是:Lyft’s iOS app
從 iTunes 下載下來的 Lyft 是一個 .IPA 檔案(其實就是 zip 格式的)。它有很多的元資料,像 Info.plist 檔案,資原始檔,圖片,本地化的字串以及很多類似的檔案。這裡面也包含一些可執行檔案,那些被編譯了的程式碼就在裡面。另外,因為 Lyft 是用 swift 寫的,所以還包含很多附加的框架。
Lyft 現在看來是一個完完全全的黑盒子。我們不能直接的看透它,但我們依然有能力從特定的一些角度窺視到。一種方法就是通過監測網路流量來觀察他的 API, 同時還能看到有哪些資訊被它傳送到了 Lyft 的伺服器,這些通常對 App 而言是沒有什麼傷害的。
然後,我們還能 注入程式碼,真的非常爽。當 app 執行的時候,你可以打探打探這些 app 裡被例項化而且存活的物件。當停止執行的時候,我們可以嘗試去研究下 .app 是如何被組織的,同時通過特定的視角來看這些程式碼。
探測網路流量 - Charles 演示 (4:08)
Charles 是一個 HTTPS 代理工具,它可以讓流量在發往伺服器前攔截網路流量。我們可以用這個工具來觀察所有從 Lyft app 流向伺服器的請求。
Lyft 事實上通過 SSL 加密了它的所有流量。但這些都很好破,通過中間人攻擊的方法來解決這個問題。其實就是替換了加密證書。
當你在用 Lyft 的時候,Lyft 不斷的把你的地理位置發給 Lyft 的伺服器。 對車輛的請求和取消等操作會以格式化後的 JSON 在 Charles 中呈現出來。用 Charles,你會看到 app 裡和網路互動的所有內容。
向 Lyft 裡注入程式碼 - Cycript 演示 (7:44)
下一個工具真的很贊,叫 Cycript。使用這個工具的時候需要一個越獄裝置,注入程式碼到 app 裡。Cycript 可以讓你看到別人的程式碼,當然你自己寫的 app 也逃不過。這個工具是 Objective-C 和 Javascript 的混合 app,只用輸入 Objective-C 的程式碼到控制檯裡,就能在 app 執行這些程式碼。它的互動程式設計環境(REPL)比 LLDB 的要好很多。儘管它不能中斷,設定斷點等等,但他對執行時程式碼非常友好。你可以直接輸入下面的程式碼,基本上都是 Objective-C :
var application = [UIApplication sharedApplication];
[application openURL:[NSURL URLWithString:@"https://google.com"]];
我 SSH 進入我的破解過的 iPhone 6 以後,開啟 Lyft,執行 Cycript,我可以通過選擇功能來獲取一個執行時的類例項,比如 view controller 的,或者統計類的例項。作為開發者,要確定設定 ACL 來防止特定 api key 的許可權問題。如果 app 能拿到這些 key,那 Cycript 也一定能拿到。
你也可以修改 app 裡的 views。比如,用 UIApp.keyWindow.recursiveDescription 通過記憶體定址,把 “呼叫車輛” 的按鈕變成綠色。
var b = new Instance (ADDRESS)
b.backgroundColor = [UIColor greenColor]
Cycript 甚至還支援 tab 智慧提示。這個功能不但對於逆向很有用,你還可以來測試你自己的 app,比如快速換個顏色測試測試效果什麼的。
Q: 用 Crcript 測試自己的 app 的時候,裝置一定需要越獄麼?
Conrad: 對你自己的 app 的話,並不需要。在 cycript.org 官網上,提供了一些如何把這個工具嵌入到你自己的 app 的文件。
解密可執行檔案 - dumpdecrypted 演示 (11:28)
接下來,我們就要來感受下 app 執行時真正的程式碼了。這個稍微有些複雜,而且需要裝置越獄,我們的演示會比較簡單。因為有能力重簽名我們的裝置,所以蘋果會加密商店裡所有的 app,防止 app 被大家共享。然而,對於一個越獄裝置來說,所有的這些加密的 app 都是可以被解密的。我從其他人那裡 fork 了一個 repo,叫 dumpdecrypted,原作者寫這個是為了匯出一個 app 的資源,我 fork 了一份,是為了讓它能夠支援所有的框架,畢竟現在很多 app 都含有 frameworks。
用它的時候,你只要簡單的 clone 到本地,然後執行 make
,再在你越獄後的裝置上對 app 執行一下。我對 Lyft 的 app 執行了這個,很快就解密了所有的檔案。你看這些檔案的時候,會發現所有的 framework 都以 .decrypted
字尾結尾。比較有意思的一些模組是
Lyft,LyftKit 和 LyftSDK,但是我們還發現了它還用了 SocketRocket,Stripe,Pusher,Mixpanel 等等的庫。
分析可執行檔案 - IDA 演示 (13:47)
要分析我們匯出的檔案,檢視原始碼,我們要到 IDA,和 Hopper 及 class-dump 類似的一個工具。當在編譯一個 app 的時候,XCode 會建立一個由彙編組成的可執行檔案。IDA 能很漂亮的輸出這些可執行檔案裡的彙編程式碼,並且相互連線起來,你因此會看到一個由彙編程式碼組成的圖。儘管 IDA 很貴,但是它的免費版本 已經夠用了。除了 64 位以外,基本都有。
要找到 Lyft 的 URL scheme,我們可以在 IDA 裡執行一個簡單的搜尋,關鍵字是 openURL
,在類似的反編譯工具中,Objective-C 是相對友好和容易的,因為它對於工作原理是相對透明的,Swift 就相對較差,很多工具還沒有很好的支援 Swift,Swift
的彙編程式碼也更混亂,它的類資訊很難被提取出來,不過,多練習練習,看看這些彙編,你會發現一些技巧來捕獲你所想找的資訊。
使用 IDA 的圖表視野,我們發現了_TZFV4Lyft15DeepLinkManager13handleOpenURLfMS0_FCSo5NSURLSb
,看起來是個很亂的字串,然而我們依然可以發現 LyftDeepLinkManager
, handleOpenURL
,
和 NSURL
這幾個關鍵詞。這些看似隨機的字串其實是有解釋的,雖然沒有文件來說明。如果想要了解這些字串的含義,可以看看 Mike Ash 的部落格裡一篇叫做 Friday
Q&A 的文章,這些模糊的東西都有解釋。比如_T
意思是這是一個 Swift 的符號,F
意思是這是一個函式,4Lyft
是一個模組名稱,等等。
尋找 Lyft 的 URL Scheme (18:23)
想要從我們所見之中發現 URL scheme,我們就要用開發者的思維來思考。
一個 URL scheme 結構大概是這樣的:
lyft://action?parameter=value
我們現在要去找到 action
是什麼,parameter
可能會是哪些。我首先找到了 DeepLinkAble
Swift
協議,瞭解了深度連結的工作原理。通過搜尋 DeepLinkAble
關鍵字,我在一個類裡發現了諸如 ride
,help
,invite
,profile
的請求物件。
我們對如何開始一次打車很好奇,我們可以看一下 Lyft 的 DeepLinkToRide
類,看看他是如何工作的。想要看這些,你甚至不需要了解彙編,你只需知道如何搜尋和掃描彙編中你需要的資訊即可。就好比你即使不會法語,卻經常看見到法語,看的足夠多次後,你總是能悟出些東西來。很多時候
IDA 展示給我們的完全就是一種外語,其實我第一次嘗試逆向 Swift 的時候,就是這樣一種感受。
通過大致瀏覽 DeepLinkToRide
的圖。我發現了一些不同的字串,比如 “pickup”,“[latitude]”,“[longtitude]”,“destination” 等等。IDA 還展顯示了 ”ridetype“,後面經過測試發現這是個 action,通過檢視
“ridetype”,我們發現了 “lyft”, “lyft_line”, “lyft_plus”, 和 “access”,這些都是出行方式。
構造這些不同的 URL 部分,然後測試請求。我發現請求一個出行的 scheme 方式如下:
lyft://ridetype
?id=lyft_line
&pickup[latitude]=0
&pickup[longitude]=0
&destination[latitude]=0
&destination[longitude]=0
現在,我明白了 Lyft 是如何工作的了,我可以整合到 Workflow 裡了。這個過程其實就是二進位制分析。儘管這些看起來很複雜,但事實上,只要像個開發者一樣思考,其實也那麼的難。
Q: 證書繫結(pinning certificates)能否防止中間人攻擊?
Conrad:證書繫結是一個用來防止中間人攻擊的方法,在實踐中確實很有用,前提是你的手機未越獄。比如 Twitter 就在用證書繫結技術。然而,用逆向工具 Cycript 能夠輕鬆破解。AFNetworking 支援證書繫結,只要設定一下 SSLPinningMode
這個屬性。然而…
我們可以用 Cycript,再把它修改成 none
,如果你的 iPhone 越獄了,或者被人控制了。所以,並沒有一種方案能夠徹底防止你的流量被檢測,如果沒有越獄,倒是一種很好的保護方法。
Conrad: 字串混淆有以下的好處:
- 如果你用蘋果框架裡的私有 API,能很容易的躲避過蘋果審查的自動掃描工具。
- 如果那個人並沒有一部越獄的 iPhone,或者它不知道如何解析混淆,他可能很難找到 app 裡的字串。
然而,你無法永遠的把字串藏起來。比如,在 Cycript 中,你可以輕易地解開混淆。所以字串混淆也不是沒法破的保護方案。
Conrad: 他們事實上是不同的工具。dumpecrypted 能將一個 App Store 加密的工具解密。而 class-dump 能將一個解密後得二進位制檔案去除他的 Objective-C 介面檔案,跟 IDA 有點像,不過很遺憾的是:它不支援 Swift。
Q: 匯出 Apple framework 的庫,比如:this one,是不是也是用的 class-dump?
Conrad: 是的,蘋果的框架沒有加密,可以被輕易地匯出所有的class。這意味著那些把蘋果的私有interface 放到GitHub上的,你可以輕易地搞定他們。
Q: 逆向工程是否存在法律問題?Lyft 會不會不同意呼叫他們的私有程式碼和 api?
Conrad: 從法律上講,我覺得沒有太多問題。我們通常會跟所有的合作伙伴去聊到我們在做的事情。比如:我剛剛發現的這些東西我都會和 Lyft 討論一下,在他們允許後集成到 Workflow 裡。這些技術其實也有很多道德上的考慮。不過話說回來,這些技術也能阻止開發者們胡亂搞,就比如之前逆向 Twitter 發現他們上傳使用者手機裡裝的 App 列表到他們的伺服器上。所以凡事總有兩面,逆向工程只是個工具,善用就好了。
Q: 如何檢視蘋果框架的反彙編程式碼?
Conrad: 這個的過程跟我之前展示的一個很像,只不過你不再需要一個越獄後的裝置。當你把線插到裝置上的時候,iTunes 實際上獲取到了裝置的符號,你可以在 Xcode 裡找到一個大概叫 “device symbol” 資料夾,在 IDA 或者 class-dump 裡開啟 Apple 的 frameworks,然後展開分析。這些都是沒加密的,而且可以直接拿來用。在修復一些 beta 版本的 UIKit 的 bug 的時候很好使。
https://realm.io/cn/news/conrad-kramer-reverse-engineering-ios-apps-lyft/