🙈 如何隱藏你的熱更新 bundle 文件?

📌

如果你喜歡我寫的文章,可以把我的公衆號設爲星標 🌟,這樣每次有更新就可以及時推送給你啦。

前段時間我們公司的一個大佬從一些渠道得知了一些小道消息,某國民級 APP 因爲 Apple App Store 審覈人員檢測出 React Native 熱更新的內容,被拒審了三個月。我們的熱更新平臺和出事的 APP 原理相似,所以也存在着拒審危險。那麼我們就要想一些辦法,隱藏熱更新 bundle,不被審覈人員發現。

其實這個問題蠻複雜的,因爲它不單純是一個技術問題,還涉及到各種複雜的商業利益,在諸多的限制條件下,你很難去找到一個最優解。而且這個問題也比較敏感,我也只能大致講一下我的思路,具體的代碼實現本文也不會提供。

📌

鄭重聲明:若有人按本文思路隱藏熱更新數據導致應用拒審或下架,本人概不負責

一、商業利益

Apple 公司對 iPhone 生態有着非常嚴格的管控:App 上架必須走 App Store,動態鏈接庫要參與簽名,帶 JIT 功能的虛擬機不能用......

對於熱更新技術,Apple 在 2017 年封殺過一次 JSPatch 這個熱更新框架,導致很多的 APP 被拒審,根據 Apple 官方給出的理由,主要有三點:

  • 熱更新代碼沒有做好加密和校驗,有可能被第三方破解劫持
  • JSPatch 權限過高,可能會調用私有 API,改變原有的 APP 功能
  • 對於 Apple 官方來說,JSPatch 自由度太大,會繞過 App Store 這個 iOS 上的唯一流量分發平臺更新應用,影響商業利益

俗話說得好,斷人財路如殺人父母,這種涉及商業利益的事情無論放在誰都頭上都忍不了,而且很多應用又不是微信,有龐大的用戶基數可以和 Apple 官方談判(微信小程序生態就是談出來的,但是小程序支付權限就沒談妥),所以說這個問題還是很複雜的。


其實對於 Apple 官方來說,對與動態化熱更新的態度向來是不贊成也不反對,和 JSPatch 比起來,React Native 和遊戲熱更新這兩種應用場景還是被允許的,主要還是體現在三點:

  • 網遊這種重運營的場景還是需要熱更新維持活動熱度的,每週都有新活動,讓用戶主動去 App Store 下載更新包很不合理,App 活動運營同理
  • React Native/Lua 等熱更新技術是在一個容器裏進行動態化的,不像 JSPatch 有那麼大的修改權限
  • 蘋果官方在商業利益上和遊戲廠商/互聯網巨頭達到一些微妙的平衡

說實話蘋果審覈一直很迷,拒審有時候和打太極一樣,給出的規範各路解讀都不一樣,不過爲了保險起見,我們還是要研究一下相關的平臺規範。

二、解讀規範

2015 年蘋果發過一篇協議——《Apple Developer Program License Agreement》,文中第 3.3.2 節有一段關於熱更新的內容:

📌

Except as set forth in the next paragraph, an Application may not download or install executable code. Interpreted code may only be used in an Application if all scripts, code and interpreters are packaged in the Application and not downloaded. The only exceptions to the foregoing are scripts and code downloaded and run by Apple's built-in WebKit framework or JavascriptCore, provided that such scripts and code do not change the primary purpose of the Application by providing features or functionality that are inconsistent with the intended and advertised purpose of the Application as submitted to the App Store.

這一段話大概就是說除了 Webkit 和 JavascriptCore 可以動態執行下發的腳本和文件,其它所有腳本/代碼/解釋器都必須打包在 APP 內部。這句話其實就給 React Native 留了一個口子:React Native 就是用 JavascriptCore 執行 JS 腳本文件的,那麼動態下發也是合理的。


📌

Interpreted code may be downloaded to an Application but only so long as such code: (a) does not change the primary purpose of the Application by providing features or functionality that are inconsistent with the intended and advertised purpose of the Application as submitted to the App Store, (b) does not create a store or storefront for other code or applications, and (c) does not bypass signing, sandbox, or other security features of the OS.

這一段話大概就是說,我允許你熱更新,但是必須遵循我這三條規定:

  • 不能大的修改 APP 功能,導致應用實際功能和 APP Store 的宣傳不符(這個地方就很打太極,評判標準全靠審覈人員心情)
  • 不能動態創建應用商店(應該是不能繞過 IAP 支付的意思,要不然怎麼收蘋果稅)
  • 不能繞過簽名/沙箱/OS 的安全功能(這個可以理解,維護系統和生態安全)

這樣解讀下來,貌似只要按照規範當個良民就可以解決問題了。但是說實話,動態化規範更多的是君子協議,如果雙方都講武德,那大家其樂融融都挺好;萬一哪個人跳出來要壞規矩,說實話大家都很難堪。在未來,熱更新技術肯定還是要以微妙的平衡狀態存在下去。

三、技術實現

每次設計一些工程方案時,我個人的習慣都是先從理論上找答案。就拿隱藏熱更新 bundle 這個例子來說,我們主要是想在信息傳輸這裏找到突破口,實際上香農老爺子 1949 年就提出了一個「香農一韋弗通信模型」。這個模型裏把通信分爲五個部分:信息源發射器信道接收器信息接受者噪音

香農一韋弗通信模型
香農一韋弗通信模型

那麼結合這個通信模型,我們隱藏/加密通訊信息的答案就呼之欲出了:

  • 對信源加密:在信息的收發終端發送消息時加密,接受消息時解密
  • 對信道加密:信息在信道傳輸時,經過信道時進行加密

那麼我們下面就對這兩個大方向進行擴展和探討。


1.對消息本身加密/混淆

1.1 隱寫術——當代特洛伊木馬

隱寫術是一個非常非常古老的技術,這個技術的關鍵就是把想要傳遞的數據隱藏/僞裝一下,不讓第三方看出來真實想要傳遞的數據。

隱寫術的例子非常多,比如說特洛伊木馬,你從外面看是個木馬,但運到城裏,士兵就跑出來了;我們看的一些影視劇裏,也有類似原理的橋段:主角收到一份無字信紙,在蠟燭上一烤,文字就顯現出來。如今的數字時代肯定不會用無字信紙祕密傳遞消息,我們肯定有些更加賽博的方法,比如說圖種技術——把消息隱寫到圖片文件裏

如果大家玩過一段時間貼吧,對圖種技術肯定不會陌生,有些大神會發個貼,把種子文件隱藏在圖片裏,大家把圖片下載下來,把 .jpg 的後綴改爲 .zip or .rar,然後解壓文件就能得到隱藏的種子文件,然後在貼吧留下「樓主好人」的美譽。

那麼圖種技術的原理是啥?其實很簡單,它只是單純的把一個 jpg 文件和一個 rar 文件合併在一起,但是圖片查看器會忽略附加的 rar 文件數據,這樣在感官上這是一張圖片,但是從二進制的角度看這個圖片文件裏隱藏了一些數據。

下面我們看看圖種文件的原理。


首先我用圖片編輯器生成一個 2x2 4 個像素大小的圖片——RGBY.jpg。顏色我參考 Google logo 配了一下:

RGBY-image
RGBY-image

然後我們用二進制查看工具(我這裏用的是 Hex Fiend 軟件)查看這個圖片的編碼,因爲圖片只有 4 個像素,所以二進制數據也會比較小,注意觀察這個文件的二進制數據,它是 FF D8 開頭,FF D9 結尾的。

圖片查看器加載一張圖片文件時就會做檢測,如果是 FF D8 開頭,就會認爲這是一張 jpg 圖片,然後就會進入 jpg 圖片解碼的分支,加載二進制數據遇到 FF D9 後,就會認爲這個圖片已經加載完畢,後面的數據就不會再管了

RGBY-Binary-Code
RGBY-Binary-Code

基於圖片預覽器不會加載 FF D9 之後數據的這個特性,我們可以把一些要隱藏的數據附加到 jpg 文件之後。

這裏爲了測試方便,我新建了一個內容爲 hello wordtext.txt 文件,然後用 cat 命令把 RGBY.jpgtext.txt 合併一下,生成 RGBY_text.jpg 文件:

cat RGBY.jpg text.txt > RGBY_text.jpg

這時候用圖片瀏覽器查看文件,可以看出文件還是正常預覽的:

RGBY_text-image
RGBY_text-image

但是用二進制查看工具查看這張圖片,就會發現他在末尾多了 11 個字節,正是 text.txt 裏的內容—— hello word

RGBY_text-Binary-Code
RGBY_text-Binary-Code

這樣我們就達到了隱寫的目的。


大家不要覺得這個方案 low,實際上阿里的一些密鑰就是通過類似的原理寫到一張圖片裏的(當然不會像以上案例那麼簡單)。我們在傳輸熱更新 bundle 文件時,可以把 bundle 文件隱寫在一張圖片裏,這樣審覈人員在做流量監控的時候,抓包看到的是一張圖片,如果不檢查圖片的二進制編碼,是不會發現裏面隱藏了數據的。

針對這種方案,服務端和客戶端的改動都比較小,服務端只需要每次下發 bundle 時前合併一個圖片文件,客戶端讀取隱寫圖片後去掉多餘的圖片數據就可以了。


當然隱寫術還有很多種,比如說基於 LSB 的圖片隱寫技術,把數據寫在 jpg png mp4 的擴充數據字段裏,因爲原理大同小異,這裏就不多介紹了,感興趣的同學可以自行搜索學習。


1.2 對稱加密

對稱加密也是一個歷史悠久的加密技術,在信息技術的加持了下也飛速發展,我舉個最簡單的對稱加密算法——異或算法加密

異或運算我想每一個程序員都不陌生,我們先約定 0 爲 false, 1 爲 true,那麼 XOR 運算的真值表如下:

A B A ⊕ B
0 0 0
0 1 1
1 0 1
1 1 0

從真值表可以很容易推出下面的運算法則:


運用上面的運算規則,我們假設 密鑰,對內容 加密,那麼得到的密文就是 ;想對密文解密,只要讓密文和密鑰 再進行一次異或運算就可以了:

我們可以用代碼舉例子驗算一下:

// 加密:
// 原文       密鑰       祕文
01010111 ^ 11110011 = 10100100

// 解密:
// 祕文       密鑰       原文 
10100100 ^ 11110011 = 01010111

衆所周知,位運算都是非常快的,如果要簡單地對 bundle 做個混淆,直接用異或加密,基本上不會影響性能。

雖然異或運算很簡單,但是密碼學有個第一準則:永遠不要自己實現加密算法。我們可以用已經非常成熟的對稱加密算法(例如 AES 和 DES)對 bundle 進行加密:性能高,安全性好,最重要的是開源社區都有現成的庫,直接調包就可以了。

所以如果用對稱加密的方案,只要服務端和客戶端商量好一個密鑰,然後服務端用密鑰加密 bundle,客戶端用同一個密鑰解密,就能在一定程度上繞過 App Store 的異常流量檢測。


1.3 非對稱加密

非對稱加密是屬於近代密碼學的內容了,非常的新,但是也非常的可靠,具體原理太複雜了,一句兩句根本說不清楚,我就不做介紹了。

在加密熱更新 bundle 這個場景下,其實和對稱加密的效果差不多,只不過換成私鑰加密公鑰解密了。


1.4 總結

一般來說,對 bundle 加密不會單純使用一種技術,比如說我們會用混合加密的方式對 bundle 本身加密,用消息認證碼(例如 HMAC)防篡改,加入時間戳隨機數防重放,最後再把加密後的數據進行隱寫......這裏面的組合實在是太多了,個人認爲參考一些經典的加密組合進行業務實踐即可。


2.對信道加密

信道加密在本文的場景下也比較直觀,就是使用 HTTPS 協議,目的就是防止審覈人員通過抓包的方式捕獲到我們的熱更新流量。當然 HTTPS 也有很多的有意思的知識點,下面我就簡單介紹一下。


2.1 使用 HTTPS

2021 年了,我想互聯網上基本沒有裸露的 HTTP 明文流量了吧......前幾年可能還會有企業考慮 HTTPS 加密帶來的服務器成本,但在各大平臺(iOS/Android/Chrome)的要求下,除了個別無人維護的網站,基本都全站上 HTTPS 了,畢竟現在數據的價值遠遠高於服務端的電費,上了 HTTPS 後,起碼被中間人攻擊被劫持的概率會降低不少。

上 HTTPS 就高枕無憂了嗎?那肯定不是。我去年寫過一篇 Charles 抓包的文章,裏面花了大量的篇幅去介紹 HTTPS 抓包。既然一個 APP 開發者可以藉助市場上的工具進行抓包,那麼審覈人員更可以了。在抓包工具下,大部分 HTTPS 數據都可以被捕獲和劫持。下面我們就說說 HTTPS 協議中一些比較高階的內容。


2.2 HTTPS 證書固定

HTTPS 證書固定,又叫 HTTPS 證書鎖定,英文名爲 Certificate Pinning,指的是我們在 APP 內置僅接受指定域名的證書,而不接受操作系統或瀏覽器內置的CA根證書對應的任何證書。

通過這種授權方式,我們可以保障 APP 與服務端通信的唯一性和安全性。如果開啓了抓包軟件,不主動導入固定的證書,就無法有效的抓包(具體原理可看我的博文:Charles 抓包原理)。我想審覈人員還沒那個精力去砸殼你的 APP 獲取你的證書,所以可以通過這種方式隱藏你的熱更新 bundle。

當然,證書固定也是有一定代價的。CA 簽發證書都存在有效期問題,所以缺點是在證書續期後需要將證書重新內置到 APP 中。


2.3 HTTPS 雙向認證

我們平常使用 HTTPS 時,一般只做了單向認證,即客戶端認證服務端的真實性。其實 HTTPS 支持雙向認證的,即支持服務端認證客戶端的真實性(具體流程可見下圖 * 部分)。

TLS 1.2 握手流程圖
TLS 1.2 握手流程圖

一般來說開啓 HTTPS 雙向認證的 APP 都是那種安全性要求極高的 APP,比如說金融類 APP 和匿名社交類 APP。而且想要實現雙向認證,就必須要在客戶端內置一份公鑰證書和私鑰,但 APP 又有砸殼風險,所以還得想辦法把這兩個東西加密和隱寫(都成俄羅斯套娃了)。

綜合來看,實現 HTTPS 雙向認證的成本還是很高的,但是一旦實現,安全係數還是非常高的,不僅僅是繞過審覈人員的流量檢測,綜合來看整個 APP 的網絡安全都得到了極大的防護。


四、總結

對於熱更新這件事,根據 Apple 的應用規範,基於 JavaScriptCore 的熱更新是完全可行的,但前提是你必須守規矩,不能脫離 Apple 的掌控範圍;但是 App Store 的審覈規則又極其不透明,雖然我們是良民,但是一定程度上還是要隱藏一下熱更新 bundle,規避不必要的麻煩;隱藏熱更新 bundle 我們可以從信源加密和信道加密兩個角度去思考,綜合來看就是靈活利用密碼學知識,對網絡數據進行加密,防止被檢測出異常流量,隱藏 bundle 的同時,也保護了用戶的數據安全,降低被攻擊的可能性。

五、參考閱讀

🍶 爲什麼你的 Charles 會抓包失敗?

📌

如果你喜歡我的文章,希望點贊👍 收藏 📁 在看 🌟 三連支持一下!!!謝謝你,這對我真的很重要!

歡迎大家關注我的微信公衆號:滷蛋實驗室,目前專注前端技術,對圖形學也有一些微小研究。

也可以加我的微信 egg_labs,歡迎大家來撩。

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