黑科技,用这个工具来对任意方法进行Hook
/? ?今日科技快訊? ?/
近日,抖音電商發布《2021抖音電商消費者權益保護年度報告》(下稱“報告”),從內容治理、商品管控、客服保障、知識產權保護和信息安全等五個維度,全面呈現了去年該平臺在消費者權益保護方面的舉措與成果。報告顯示,抖音電商先后推出16項消費者權益產品。全年支付1.8億元保障基金,用于幫助消費者維權。通過“全密文保護”等風控措施,保障消費者信息安全。此外,平臺還升級了知識產權保護體系。
/? ?作者簡介? ?/
本篇文章來自我是技術男的投稿,文章主要分享了他通過ASM基礎開發的方法替換工具的整個過程,相信會對大家有所幫助!同時也感謝作者貢獻的精彩文章。
我是技術男的博客地址:
https://www.zhihu.com/people/niu-xiao-wei-78-52
/? ?前言? ?/
ReplaceMethod:在代碼編譯階段,根據收集的配置信息,利用ASM對字節碼進行替換,以達到對調用的方法進行替換的工具(您不需要學習怎么寫gradle插件,不需要學習ASM非常復雜的語法)
/? ?為什么要做這個工具? ?/
治理項目中的線程問題
背景
由于我做的項目歷史非常的悠久并且非常的龐大復雜,項目中的線程沒有一個統一的管理方式并且野線程(沒有名字的線程)到處飛。于是想著使用ASM在編譯過程中對所有new Thread的地方進行替換到某個方法中,在這個方法中來統一處理 統一管理。
在做輕量級LayoutInspector工具時候, 需要定位view被inflate的位置信息(哪個類的哪個方法哪行)以及view的點擊事件的位置信息。想到的辦法也是同上面(利用ASM在編譯過程中替換字節碼來實現)。
于是我就想不能每次遇到對方法替換的時候就要寫重復的寫gradle插件,并且在寫ASM相關的替換代碼,那我為啥不寫一個這樣的工具呢(并且ASM相關的api真的很復雜),并且這個工具是可以做很多事情的。
/? ?用它能做什么? ?/
下面列舉了幾個例子
對view.SetOnclickListener方法進行替換(以及對其他的點擊事件進行替換)
比如代碼中的所有的view.setOnClickListener方法最終被下面的方法替換。
public?static?void?setOnClickListener(View?view,?View.OnClickListener?clickListener,?Object[]?objects)?{view.setOnClickListener(new?ClickListenerWrapper(clickListener,objects));????? }public?static?class?ClickListenerWrapper?implements?View.OnClickListener?{private?View.OnClickListener?listener;private?Object[]?params;public?ClickListenerWrapper(View.OnClickListener?listener,Object[]?objects)?{this.listener?=?listener;params?=?objects;}@Overridepublic?void?onClick(View?v)?{String?className?=?params[0]+"";String?classSimpleName?=?className.substring(className.lastIndexOf(".")?+?1);Log.i(TAG,?"click?info:?("?+?classSimpleName?+?".java:"?+?params[3]?+?")"?+?"?or?("?+?classSimpleName?+?".kt:"?+?params[3]?+?")"+"???view:"+v+"??clickListener:"+listener);if?(listener?!=?null)?{listener.onClick(v);}} }上面代碼最終的效果是在view被點擊的時候,會打印當前setOnClickListener的具體代碼處(類名,方法,行數)。
對各種new Thread()進行治理
如下把代碼中的甚至是第三方庫中的所有new Thread()的代碼統一都轉入下面的方法中生成線程。
public?static?Thread?createThread()?{//使用統一的創建線程的方法重新生成Thread,??? }排查修復隱私問題(眾所周知現在隱私問題國家管控的非常嚴格),可以對涉及隱私的方法調用進行替換
如下例子,獲取手機mac的代碼如下:
WifiManager?wm?=?(WifiManager)?context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);???? WifiInfo?wi?=?wm.getConnectionInfo();????? wi.getMacAddress()可以對項目中所有的wi.getMacAddress方法替換到下面方法中,在這個方法中就可以寫自己的邏輯了:
public?static?String?getMacAddress(WifiInfo?wi)?{//?增加自己的邏輯,如添加log信息String result = wi.getMacAddress();return?result; }對第三方jar或aar的方法進行替換
比如對第三方jar中的Log進行攔截,把需要的關鍵的log信息存儲下來,或者修復第三方的bug
大家還可以根據自己的需要來做其他更有趣的事情
/? ?實現原理? ?/
在說實現原理之前,先看下使用ReplaceMethod替換方法的例子。對Activity的setContentView(int)方法進行替換例子。原先代碼:
替換后的class。
最終調用的方法。
上面例子展示了對Activity的setContentView(int)方法替換。
會在當前的類中生成一個私有的靜態的方法(當前的類實例及setContentView的參數作為參數)
判斷當前類是否是Activity的子類,是的話則調用ReplaceMethodDemo.setContentView(Activity, int) 方法,至此setContentView(int)方法被替換為ReplaceMethodDemo.setContentView(Activity, int)
若當前類不是Activity的子類,則還是執行之前的邏輯
原理
看了上面的替換結果,我想大家冒出來的第一個問題是,替換不應該是 xxx.invokeAMethod -----> 被替換為yyy.invokeAMethod 這么簡單嗎?為啥要生成私有的靜態方法這個啰嗦的不在?關于這個問題在下面給與答復。
方法替換的本質就是:xxx.invokeAMethod -----> yyy.invokeAMethod
圍繞本質 ,實現主要做三件事情。
收集替換信息
收集替換信息主要是在**.gradle文件中進行配置(為啥沒有采用在txt文本文件中進行配置的主要原因是,配置項目確實很多,在文本文件中配置起來非常麻煩,出現錯誤難以定位問題),具體的配置介紹會在后面介紹 收集需要替換的方法信息,在編譯過程中通過ASM,查找到替換的方法后,插入替換者的信息字節碼。
定位替換方法
利用ASM,根據收集到的信息,去定位具體的方法,定位的時候主要對比。方法的owner(所屬類),方法是靜態的還是實例,方法的名稱,方法描述符。在定位確定的方法,比如靜態方法調用 StaticClass.invokeStaticMethod()(并且StaticClass的父類中沒有定義invokeStaticMethod這個方法)的時候,是非常的簡單的,在定位調用的是父類的靜態/非靜態方法是不能正確定位的,如上面例子對Activity類的setContentView(int)方法替換,在ChildActivity(Activity子類)調用setContentView(int)方法,這時候的owner是ChildActivity ,它和Activity肯定是不一樣的,這時候就會導致定位不到,但是ChildActivity中調用的setContentView(int)確實是需要替換的方法,那因此針對這種情況需要特殊處理,處理方法如下:
在當前類中生成一個私有的靜態方法,它的參數有(當前類作為第一個參數,替換方法的參數。靜態/實例方法的參數是不一樣的),它的返回值與替換方法保持一致
在該靜態方法中加入一些判斷邏輯,判斷第一個參數是否是替換方法owner的子類,是的話進行替換,不是則保存原先邏輯
把調用替換方法的地方替換為調用生成的私有靜態方法
這樣,就可以在運行期間來進行檢測定位邏輯和替換邏輯了。
替換
定位到替換方法后,利用ASM插入對應的字節碼,主要分為幾種情況:
對于確定的方法的方法,直接替換為目標類的方法名(目標類指yyy),替換后的效果:xxx.invokeAMethod -----> yyy.invokeAMethod
對于new對象的方法,直接替換為目標類的靜態方法(目標類指yyy,靜態方法的參數與構造函數參數一致,返回值為new對象對應的類),替換后的效果:new MyClass( int ,int ) -------> yyy.createMyClass( int, int)
對于調用的是父類的靜態/非靜態方法,利用ASM在當前類中插入私有靜態方法(見 2 定位替換方法),替換后效果:xxx.invokeAMethod -----> 當前類.generateStaticMethod ------> yyy.invokeAMethod
接入(參考代碼中的例子)
工程的build.gradle文件
buildscript?{repositories?{maven?{?url?'https://jitpack.io'?}}dependencies?{classpath?"com.github.niuxiaowei:ReplaceMethod:1.0.0"} } allprojects?{repositories?{maven?{?url?'https://jitpack.io'?}} }app的build.gradle文件
apply?plugin:?'ReplaceMethodPlugin'replaceMethod{open?trueopenLog?truelogFilters?"IInterfaceTest"replaceByMethods{register?{replace?{invokeType?"ins"className?"android.view.LayoutInflater"methodName?"inflate"desc?"(int,android.view.ViewGroup)android.view.View"}by?{className?=?"com.mi.replacemethod.ReplaceMethodDemo"methodName?=?"inflate"addExtraParams?=?true}}} }在replaceMethod中進行配置,可以參考代碼中的例子。
replaceMethod中配置項介紹
open
true替換功能打開,false替換功能關閉
openLog
true編譯過程中的log打開, 否則關閉(日志建議不要打開,否則影響編譯速度)
logFilters
配合openLog使用,只有在openLog為true的情況下才有效。不配置則會把所有日志打印出來,配置后只顯示配置的日志,可以配置多個,用","分割,如:logFilters "IInterfaceTest","Main","AA"
replaceByMethods
注冊多個替換方法
register
注冊一對replace by??梢赃@樣理解register,replace中的方法被by方法替換
replace
配置需要替換的方法,它的屬性有:
invokeType:代表方法類型靜態的,實例,構造方法,它的值有static(靜態方法),ins(非私有實例方法),new(構造方法)
className:方法所屬的類名, 配置內部類時候必須使用"\\$", 如className "(android.view.View\$OnClickListener)"
methodName:方法的名稱
desc:方法描述符配置,格式為(paramType, paramType2,...) returnType。paramType是基本數據類型直接用基本數據類型,否則使用類的全路徑,多個param直接用 "," 分割 returnType。同上,代表返回類型,返回類型為void,可以不用配置若方法沒有參數并且返回類型為void,則可不用配置該項
releaseEnable: 在buildType為release的時候 替換功能 是否有用。true代表該條替換在release進行替換,默認值false
ignoreOverideStaticMethod:針對子類中調用父類定義的非私有靜態方法情況,默認值為false
replacePackages:對哪些package進行替換,不配置則對所有的包進行替換,可以配置多個,如replacePackages "com.mi","com.niu"
by
配置替換replace的方法信息,它的屬性有:
className: 類名, 配置內部類時候必須使用"\\$",如className "(android.view.View\$OnClickListener)"
methodName:方法名,必須是public類型的靜態方法,若與replace的方法同名,則可不用配置
addExtraParams:是否需要額外數據,true需要,若配置為true,則在方法的參數中必須有一個Object[] 類型的參數,并且必須是最后一個參數,否則在運行過程中會奔潰,Object[] 信息有object[0] 調用replace方法的類全路徑名稱,object[1] 調用replace方法的方法名稱, object[2] 調用replace方法的方法描述符合,object[3] 調用replace方法的行信息
還未實現功能
不能對私有方法進行替換 不能對在子類中調用父類定義的靜態方法進行替換,比如對在View子類的靜態方法中調用View的inflate方法進行替換。
public?class?MyView?extends?View{private?static?void?init(Context?context,?int?resource,?ViewGroup?root){inflate(contet,?resource,??root);}}代碼地址:
https://github.com/niuxiaowei/ReplaceMethod
推薦閱讀:
我的新書,《第一行代碼 第3版》已出版!
再學一遍android:fitsSystemWindows屬性
PermissionX 1.5發布,支持申請Android特殊權限啦
歡迎關注我的公眾號
學習技術或投稿
長按上圖,識別圖中二維碼即可關注
總結
以上是生活随笔為你收集整理的黑科技,用这个工具来对任意方法进行Hook的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CC00060.kafka——|Hado
- 下一篇: 思科设备命令,网络工程师收藏!