開源位元組碼插裝工具 HiBeaver 介紹與原理詳解
介紹
插裝技術是一個古老卻又強大的技術,簡單來說就是在目標程式程式碼中某些位置插入一些程式碼或者修改成一些程式碼,從而在目標程式執行過程中獲取某些程式狀態並加以分析。這樣說可能有點抽象,我來舉個栗子。大家在不同語言的開發過程中可能都用到過一類叫Profiler的工具,開啟了Profiler之後跑程式結束後會給一個表,統計哪些函式執行的次數最多,耗時最長,用來效能優化。
Profiler工具的實現方法就是在目標程式裡的函式開頭和結尾都插入監控程式碼,在執行過程中,這些監控程式碼會記錄目標函式被呼叫的次數,並且通過一頭一尾的時間戳計算被監控的函式的執行時間。收集到的資料經過算算算,就可以出來統計的圖表。
有人說:這不就是inline hook技術嗎?是的。那我用OllyDbg不是一下子就搞定了?是卻不是。插裝的工具有很多很多(真的很多,每種語言可能有不止一個),插裝的方式也有不少(動態的,靜態的,VM的,系統的,硬體的),手工插裝在簡單分析程式中可能短平快,自動插裝卻是個學問。如果要寫個插裝程式去自動插裝另一個程式,要考慮到插哪裡,插裝什麼程式碼,怎麼確保目標程式插裝後能正常跑起來,怎麼確保插裝程式碼不會帶來太多效能開銷等等等等。這裡每一項都是一個小研究領域,避免大家瞌睡,本文暫時略過,如果大家有興趣之後可以再寫一篇學術一點的介紹程式碼插裝的前世今生。
好,回到主題, Android應用的插裝 。通常開發者以及安全分析人員都希望能夠監控APK中某些函式,比如開發人員會關心APP執行到了哪個生命週期函式,消耗多少時間。安全分析人員可能更感興趣這個APP訪問了哪些敏感的資訊,比如IMEI和網路。考慮到Android App有native code,位元組碼,甚至還有Javascript程式碼,所以設計一個成熟的Android的插裝工具要求很高。 今天來介紹一下近期開源的位元組碼插裝工具HiBeaver。
Github專案網址是:
https://github.com/BryanSharp/hibeaver
目前版本是1.2.3作者更新還挺勤快的。
HiBeaver使用案例
首先HiBeaver是一個Gradle外掛,什麼鬼?!好吧,HiBeaver被設計成在一個APP編譯中後期接入,使用大名鼎鼎的ASM.jar(不知道的去末尾的擴充套件閱讀看看)對Java位元組碼進行修改的Gradle外掛。
Gradle被小夥伴譽為: “ 一個看上去很簡單,但深入寫起來發現要完蛋的玩意” ,沒辦法,Gradle是Google Android欽定的構建系統。Gradle是一個主要用來構造Java類程式的構建系統(類似make),構建規則由Groovy程式碼提供,Gradle提供了一個框架,比如要編譯一個Java檔案可以用Groovy程式碼去繼承Gradle提供的一個類去做。
等等,等等, 什麼是Groovy?
好吧,一種比較噁心的Java方言,我們先略過,下面看了就會知道。Google Android給Gradle貢獻了一個用於構造Android APP的外掛,然後整合在Android Studio裡面。下圖是完整構建一個Android APP的流程圖,好複雜,簡單來說就是把Java檔案編譯成class之後,在用Android的dx工具編譯成Dex檔案,然後和編譯後的資原始檔打包成一個APK檔案,並進行簽名。
HiBeaver同學就是在這個過程中,在dx之前,通過Gradle提供的Transform API(大概在上圖的橘黃色的proguard前面),截獲產生的.class檔案,並且對其中的Java位元組碼進行修改(通過ASM.jar)。
話不多說,來看看怎麼用。需要使用HiBeaver首先要配置Android APP工程的build.gradle檔案,新增:
buildscript { dependencies { classpath 'com.bryansharp:HiBeaver:1.2.3' // 增加這個,HiBeaver已經發布 } }
然後在build.gradle開頭加上配置資訊:
apply plugin: 'hiBeaver' hiBeaver { // 是否列印幫助資訊,預設為true showHelp = true // 是否列印插裝log,預設是false就是列印 keepQuiet = false // 統計插裝時間 watchTimeConsume = false // 插裝規則,重點,下詳 modifyMatchMaps = [:] }
然後呼叫 gradle build
構造APK的過程中,可以看到 Applied HiBeaver
意味著配置成功了,總體來說配置還是非常簡單的。
modifyMatchMaps
是這裡的重點,也就是插裝規則陣列,每一條規則包含匹配和動作。“匹配”可以匹配到某個/些類的某個/些函式,然後動作就是具體用ASM.jar插入怎樣的程式碼了,例如以下的例子:
'*Activity|*Receiver|!android*' : [ // 首先匹配類名字,以Activity或者Receiver結尾,並且不是以android開頭,注意這裡是class全名,也就是前面包含包名 // 然後匹配成員方法 // 方法名也可以用萬用字元模糊匹配,比如這裡匹配以on開頭的方法 // methodDesc匹配方法的原型,設定成null代表不限制 // 最後adapter是插裝動作函式 ['methodName': 'on**', 'methodDesc': null, 'adapter': { // 該動作函式輸入引數包括ClassVisitor,也就是代表了匹配到的類 // access是類的訪問許可權,例如public private,詳細見java反射的Modifier類 // name, desc, signature不講了,表示原型;exceptions 是函式定義中的throw ClassVisitor cv, int access, String name, String desc, String signature, String[] exceptions -> // 這個->是一個Groovy閉包定義,接觸過Javascript的應該不難理解,沒接觸過的就認為是一個函式定義吧 // 上來第一步用cv物件找到我們指代的method MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions); // 然後定義一個修改規則 MethodVisitor adapter = new MethodLogAdapter(methodVisitor) { // visitCode是對這個目標函式的程式碼做點什麼的意思 @Override void visitCode() { super.visitCode(); // visitXXX是用來產生注入的一句Java位元組碼 // ldcInsn的意思是載入常量到堆疊,相當於x86彙編裡面push引數入棧 methodVisitor.visitLdcInsn(desc); methodVisitor.visitLdcInsn(name); // 這句是插入一句函式呼叫語句,呼叫一個鉤子函式,invoke_static表示這個鉤子函式是一個static函式,這樣也就不用傳入this指標了,這裡由於是底層位元組碼,所以要給全名,包括包名,hook函式名,還有引數定義;這裡的引數定義說明傳入兩個Object,返回void,這兩個object就是前面提到的desc和name methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "bruce/com/testhibeaver/MainActivity", "hookXM", "(Ljava/lang/Object;Ljava/lang/Object;)V"); } } // 把這個動作交給插裝框架 return adapter; }] ]
用起來就這麼“簡單”,對於比較熟悉ASM.jar的朋友們可能比較直觀。這和我們以前用OllyDbg inline hook有什麼差別呢?
首先 前面定義的匹配規則能夠選出整個程式中滿足條件的函式進行插裝,遠比人工尋找要高效,隨著規則不斷升級,匹配規則可以匹配類的繼承關係等。
其次 ,使用ASM.jar進行插裝,會自動調節指令位置,還能保證除錯資訊指向的行數依然有效,省去了很多麻煩。
上面這個例子摘自作者Github專案的README,具體例子APP也可以在Github上找到。這個例子就是在APK中使用者自己寫的Activity, Service的所有生命週期函式 (onXXX) 開頭插入呼叫 hookXM(name, desc
)
。聰明的安全人員一定能夠想到怎麼跳過APK中某些程式碼(插一條return嘛),怎麼偷樑換柱(找到某個method我就自己重寫),怎麼hook系統呼叫(找到呼叫getIMEI的地方,來呼叫鉤子函式)等等。
HiBeaver的一些好處如下:
1. 插裝規則面對的是整個APK,所以不止包括使用者自己寫的程式碼,還包括使用者使用的一些SDK,比如廣告庫,推送SDK,加密庫等等,聰明的你肯定想到了什麼。
2. 在Gradle構造流程中,在proguard混淆前,當然也在加固前,只要注入的程式碼穩定,應該不會有很大的正確性問題。
3. 靈活性,原則上聰明的使用者可以扮演一個編譯器的角色,給APK增加各種各樣的功能,而且鉤子函式(例如這裡的hookXM)依舊能用java實現。
原理分析
接下來我們來深入HiBeaver內部來看看它的原理:
首先 apply 'hiBeaver'
是將所有HiBeaver程式碼引入Gradle構造過程的入口。
程式碼在:
src/main/groovy/com/bryansharp/gralde/hibeaver/HiBeaverPluginImpl.groovy
, HiBeaverPluginImpl1.apply——
就是apply做的實際動作。Gralde裡面,所謂apply就是傳給Gradle外掛正在構建的工程物件(Project),這個工程物件裡面包含了所有已經有的過程(其他外掛的),然後當前外掛自己再往裡面加。HiBeaver這裡很簡單,就是利用了Gradle的Transform介面,註冊到工程上,這樣 Gradle每次產生了一個class檔案就會丟給HiBeaver指定的這個InjectTransform物件了。
程式碼雖然是 Groovy ,但是相當 Java ,可讀性尚可。同時可以看到,配置中如果 watchTimeConsume 是true,那麼會用工程完成處理後 project.afterEvaluate
,
去呼叫:
src/main/groovy/com/bryansharp/gralde/hibeaver/TimeListener.groovy
——
進行計時,程式碼非常直觀,也不是重點,不再贅述。
HiBeaver的重點程式碼在:
src/main/groovy/com/bryansharp/gralde/hibeaver/InjectTransform.groovy
,重點是這個覆蓋掉Gradle提供的父類Transform的transform函式。
首先 modifyMatchMaps
被從配置中讀出,並簡單處理一下。
然後作者寫到“獲取所有依賴的classPaths,僅做備用”,這裡需要稍微解釋一下:APK構造後,會產生使用者程式碼,但是使用者程式碼需要依賴的jar檔案,還有Android程式設計框架(android.jar)才能找到所有函式的連結,所以classPaths就是找全所有jar檔案。
然後HiBeaver開始遍歷所有的輸入檔案(即Gradle構建出來的所有Jar檔案以及class檔案),這邊值得注意的是工程可能會產生兩個同名的Jar檔案但是輸入路徑不同,所以這裡用了hexName進行區分,統一輸出檔案是輸入檔名+hexName。
這裡 isJarNeedModify
簡單來說就是到規則庫裡面去看這個jar檔案是否需要被插裝(匹配),如果需要,則通過 modifyJarFile
來對其進行插裝(也就是執行動作)。遍歷Jar主要會編譯使用者工程依賴的所有第三方Jar檔案,而緊跟著的遍歷目錄,一般就是使用者程式碼產生的.class檔案,也就是遍歷使用者程式碼,重寫原理類似。
isJarNeedModify
遍歷了jar裡面所有的class檔案,然後利用 shouldModifyClass
判斷是否需要修改某個class檔案。 shouldModifyClass
就是匹配規則,可以看到大大的switch裡面目前支援了三種匹配,全名匹配,簡單字串比較;正則比較,那就是用正則表示式比較;wildcard比較,也就是 *Activity
這種,用 Util.wildcardMatchPro
進行比較。
modifyJarFile
和 modifyClassFile
功能類似,一個遍歷jar檔案中所有class檔案,另一個對一個class檔案而言;最終呼叫了 ModifyClassUtil.modifyClasses
幹實際的活,這個函式傳入了整個規則組。
ModifyClassUtil.modifyClasses
是最裡層的邏輯,繼承了ASM.jar的 ClassAdapter
用來對class檔案裡面的方法進行修改。可以看到,現在的修改僅限於方法的程式碼,所以對annotation,attributes, fields全部是略過的。最終 visitMethod
方法遍歷了所有規則,匹配,並使用規則提供的Adapter對程式碼進行修改。分析到此結束。
總結
1. 基於ASM.jar還是一個比較底層的技術,大家可以學習。在傳統Java界,ASM.jar被廣泛用於實現各種Profiler,記憶體洩露檢測、程式分析工具以及Lint查錯工具等。
2. 嵌入到安卓應用編譯過程中是一個不錯的想法,但是用Groovy語言開發是在太蛋疼。
3. HiBeaver功能比較簡單,具體插裝需要使用者對ASM.jar非常熟悉,可能對開發新的插裝內容比較麻煩。
擴充套件閱讀
1. objectweb的ASM.jar:
http://asm.ow2.org/
2. Java位元組碼解析:
http://ifeve.com/javacode2bytecode/
3. Gradle Transform API:
http://tools.android.com/tech-docs/new-build-system/transform-api
4. Gradle入(cai)門(keng):
http://www.infoq.com/cn/articles/android-in-depth-gradle
5. Gradle外掛開(xian)發(jing):
http://git.bookislife.com/post/2015/how-to-develop-gradle-plugin/
6. HiBeaver作者教你怎麼用:
https://segmentfault.com/a/1190000008491823
- End -

看雪ID: kenmark
https://bbs.pediy.com/user-22148.htm
本文由 kenmark 原創
轉載請註明來自看雪社群

好書推薦:
戳 立即購買!
熱門技術文章推薦:
公眾號ID:ikanxue
官方微博:看雪安全
商務合作:[email protected]