基於多重替換方式的iOS程式碼混淆方案

TalkingData發表於2019-04-17

導語

本文將從幾個方面闡述什麼是程式碼混淆以及它的應用方法和會遇到的問題,同時分享了TalkingData自己研發的程式碼混淆方案,歡迎轉發收藏。

一、概述

1.1 什麼是程式碼混淆?

程式碼混淆,是將計算機程式的程式碼轉換成一種功能上的等價,但是難於閱讀和理解的形式的行為。程式碼混淆可以用於程式原始碼,也可以用於程式編譯而成的中間程式碼。執行程式碼混淆的程式被稱作程式碼混淆器。目前已經存在許多種功能各異的程式碼混淆器。

程式碼混淆的主要目的是為了保護原始碼,阻止反向工程。反向工程會帶來許多問題,諸如智慧財產權洩露、程式弱點暴露易受攻擊等。

本方案主要針對程式原始碼中的字串、類名以及函式名稱等進行混淆。由於此種混淆方式較為簡單,避免了由於操作中間程式碼而容易改變程式邏輯等。

1.2 程式碼混淆的通用方法

  • 將程式碼中的各種元素,如變數、函式、類的名字改寫成無意義的名字。 比如改寫成單個字母,或是簡短的無意義字母組合,甚至改寫成__這樣的符號,使得閱讀的人無法根據名字猜測其用途。

  • 重寫程式碼中的部分邏輯,將其變成功能上等價但是更難理解的形式。 比如將for迴圈改寫成while迴圈、將迴圈改寫成遞迴、精簡中間變數等等。

  • 打亂程式碼的格式。 比如刪除空格,將多行程式碼擠到一行中,或者將一行程式碼斷成多行等等。

1.3 程式碼混淆的一些問題

  • 被混淆的程式碼難於理解,因此除錯除錯也變得困難起來。開發人員通常需要保留原始的未混淆的程式碼用於除錯。

  • 對於支援反射的語言,程式碼混淆有可能與反射發生衝突

  • 程式碼混淆並不能真正阻止反向工程,只能增大其難度。因此,對於對安全性要求很高的場合,僅僅使用程式碼混淆並不能保證原始碼的安全。

基於多重替換方式的iOS程式碼混淆方案

二、實際問題

由於TalkingData合作的不乏金融類、證券類行業企業,此類行業的特點是和人民幣息息相關,成為很多不法分子來牟取不法利益的重災區,因此安全便是此類行業的相關應用程式的重中之重。以往採用尋常的方式對SDK進行編譯打包,雖然說像AppStore這樣的應用商城會採用一些程式碼級別的安全措施,但是這些安全措施也不乏被破解的可能,技術總是有缺陷的。

2.1 問題一:現有加密/加固服務的工具提供商是否滿足我們的需求

在前期調研過程中,我們測試了兩家SDK加密\加固工具,雖然兩者均能夠完成對SDK的加固,也實現了程式碼邏輯的混淆,甚至在原有的程式碼中插入了混淆視聽的“垃圾程式碼”等等,但是兩者均是通過修改程式碼編譯的中間程式碼完成的程式碼加固。此類加固方式均是通過修改原始編譯器的方式進行的操作,或者直接提供了一套修改後的編譯器替換官方編譯器,此類方式不得不說絕對是大牛作品,但是針對我們自由的SDK服務來說,此類方式可能並不適合,原因如下:

  • 通過修改系統編譯器的方式屬於黑盒方式,我們並不知曉其中是否有某些不該有的操作;

  • 編譯器的替換等操作會影響或者汙染系統純淨環境,使得打包後的SDK無法通用;

  • 此類方式編譯打包SDK後,SDK的大小會受到很大的影響,因為為了對程式碼邏輯進行混淆,增加了很多“垃圾程式碼”,以混淆視聽;

  • 此類方式對SDK的加密/加固可能會導致AppStore稽核受影響,過度混淆在AppStore是不允許的(原因是AppStore無法預知你的意圖以及是否該應用是一個殼子應用)。

2.2 問題二:開源的一些混淆工具是否滿足我們的需求

在網路上也有很多相關的程式碼混淆工具,有的甚至能夠在程式碼混淆的基礎上,加入邏輯混淆等操作,但是這些工具基本上已經停止更新,程式碼的狀態還停留在早期Objective-C的版本上,如果我們對其進行改造,所花費的時間精力有點得不償失。另外一點,開源工具是存在一定的開源協議限制的,因此所有我們來說,使用後也必須遵循相應的協議,但是面對我們當前的業務形式,暫時還不太適合。

2.3 問題三:自研混淆工具所面臨的問題

面對各種混淆工具,最終決定自研一套符合我們當前要求,並且能夠完美整合到當前打包流程中實現自動化的混淆工具,該工具和現有打包能夠完整整合,對現有SDK程式碼以及打包指令碼程式碼無侵入性。但是這裡也會遇到一些問題:

  • 打包指令碼是使用shell進行程式碼的前期準備和最終的歸檔操作的,因此我們的混淆工具是否能夠很好的使用shell調取;

  • 混淆工具的安裝、更新是否平滑,對各項現有服務無停機、延遲等影響;

  • 混淆工具是否能夠獨立git管理,在打包指令碼中進行版本檢查與更新等;

  • 面對不同的業務線,混淆工具是否能夠適配各個業務線;

  • 針對SDK的特性,部分內容是無需混淆的(例如供開發者使用的對外介面檔案等),混淆工具是否能夠有選擇性的混淆等;

  • 混淆工具是否有快取功能,針對業務完全相同的程式碼,能夠直接從歷史混淆成果中提取,為無需重複執行等;

  • 面對如上各個問題,最終我們完成了符合我們當前需求的混淆工具。

基於多重替換方式的iOS程式碼混淆方案

三、混淆工具CodeObscure

該工具主要用於Objective-C程式碼混淆,以避免class-dump等反編譯程式碼工具解讀程式碼,提高SDK的安全性。該工具最終設計為基礎配置後全自動執行,無需對現有打包指令碼以及專案工程進行其他額外操作。

3.1 基礎原理

此工具使用Ruby語言編寫。此工具會預設遍歷專案屬性、方法和類名進行混淆。當然如果簡單的進行遍歷的話,會產生無窮無盡的錯誤,因為你不能混淆Apple提供的官方API,也不能混淆framework和.a的靜態編譯的庫。所以在混淆程式碼的時候必須排除掉。該工具已經內建了過濾系統的方法。如果專案中使用Pod或者使用了靜態庫,或者其他比較特別的第三方庫,可以使用codeobscure -l [路徑1,路徑2..]的方式去過濾這些庫檔案,執行codeobscure -o [專案名.xcodepro]開始混淆你的程式碼。

當然並不意味這你執行了就一定沒錯誤,該工具最大的簡化了混淆程式碼的工作,由於不同的人編寫的程式碼可能各不相同。假設你呼叫了NSClassFromString("classNameA")而這個類正好被混淆了,它不識別classNameA到底是什麼。那麼怎麼解決這個錯誤呢。最簡單的方式就是在codeObfuscation.h中查詢classNameA並刪除它的#define即可。

!如果你執行了codeobscure -l [路徑1,路徑2..],那麼它會記錄下來要過濾的東西。如果你下次不想過濾已經過濾的庫,執行codeobscure -r來重置。

3.2 混淆方式

一般情況下,放置class-dump較為有效的方法是對程式碼進行字串替換,因此該工具著重實現了字串替換的方法,針對掃描專案工程得到的屬性、方法和類名等進行字串替換,最終通過#define的方式在codeObfuscation.h中定義。字串的替換提供了兩種字串生成方式,隨機字串隨機單詞

隨機字串,即通過隨機函式生成一定長度的字串,對屬性、方法和類名等進行等價替換;

隨機單詞,即通過隨機單詞生成工具得到隨機單詞列表,之後通過隨機函式選擇某些單詞,對屬性、方法和類名等進行等價替換。(這裡隨機單詞生成工具使用了RandomWord類庫)

注:滑動檢視完整程式碼(下同)

 // 生成結果示例:
  r :#define getHeight ZbgTCtOTDmEazebk
  w :#define getHeight nodulatedBasutoland複製程式碼

3.3 混淆字元快取

在專案迭代開發釋出過程中,大多數情況下並不會改變程式碼以及程式碼邏輯,僅僅是重新打包或者微小的改動等,此類情況下再次對專案工程進行混淆的時候,就不需要重新設定過濾的字元以及檔案。針對此種情況,該工具提供了過濾字元快取的工具,使用微型資料庫SQLite3對混淆中的一些字元進行快取,待下次使用的時候直接載入而無需去遍歷專案程式碼。

!由於Apple新版系統有SIP(系統完整性保護)預設是開啟的,所以由於安裝方式不同,可能在執行命令的時候出現:attempt to write a readonly database (SQLite3::ReadOnlyException)的問題。如果出現這個問題,請在命令列上加上sudo。

3.4 該工具目前的不足

  • 由於該工具是針對我們的SaaS產品進行的研發,因此並不適用於所有的業務線,例如一些定製版、POC相關以及其他非SaaS平臺,如果該混淆工具的最終混淆結果能夠順利的通過AppStore的嚴格審查,我們將繼續擴充套件該工具,包括業務線支援、混淆的級別等。

  • 該工具是使用Ruby語言編寫的,以及使用了SQLite3、RandomWord類庫,因此在使用前,需要配置相關的裝置,提前安裝好所需的類庫;

  • 該工具在後續使用過程中可能還會遇到不可預知的問題,因此在使用時需要謹慎對待。

3.5 安裝指南

由於本專案資源屬於內部使用,此部分暫不分享。

3.6 使用例項

使用過程中請使用絕對路徑

  • 例項1:混淆方法、類名、屬性,過濾Pods和Download

codeobscure -o /Examples/Messenger.xcodeproj  -l /Examples/Pods /Examples/Download複製程式碼
  • 例項2:僅僅混淆方法和類名

codeobscure -o /Examples/Messenger.xcodeproj -f f,c複製程式碼
  • 例項3:生成ignoresymbols檔案,用於寫入要過濾的關鍵字

codeobscure -i XcodeprojPath複製程式碼
  • 例項4: 重置-l記錄的要過濾的關鍵字

codeobscure -r複製程式碼
  • 例項5:嚴格模式過濾,並且用單詞模式進行替換。(如果程式碼中含有很多的KVO以及Runtime程式碼,使用嚴格模式,會更好的幫助你。)

codeobscure -o /Examples/Messenger.xcodeproj -t w -s複製程式碼

3.7 codeobscure -h 命令幫助

使用工具是時候,路徑直接用絕對路徑,不支援相對路徑。(直接把檔案拖到終端顯示出來的路徑就是絕對路徑)

Usage: obscure code for Objective-C project
-p, --prefix class name prefix   當前要混淆的專案類字首(TDAT, TDAA, TDGA)
-o, --obscure xcodeproj path     要混淆的專案工程xcodeproj檔案絕對路徑
-l, --load path1,path2,path3     指定不需要混淆的檔案絕對路徑
-r, --reset                      重置 -l 記錄的要過濾的關鍵字
-f, --fetch type1,type2,type3    獲取需要混淆的型別, 預設引數是c,p,f。c:類名,p:屬性,f:方法
-i, --ignore xcodeproj path      生成ignoresymbols檔案,用於手動寫入要過濾的關鍵字。eg:name,age ...
-t, --type replaceType           指定替換的形式,預設r。 r:隨機字串 w:單詞
-s, --strict                     嚴格模式過濾,並且用單詞模式進行替換。適用於KVO和Runtime比較多的程式碼
複製程式碼


四、未來願景

當前該工具已經在測試平臺通過各項打包測試,在不久的將來,我們將線上上產品的打包中看到該選項,希望該工具能夠達到預期的目標。在不斷的產品演進過程中,該工具肯定會有所過時甚至不符合實際需求,因此該工具也會隨著業務的優化改進而優化改進,盡最大努力使得該工具能夠發揮其微小“螺絲釘”式的效能。

作者:TalkingData 張永超

部分內容來源於百度百科


相關文章