日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > Android >内容正文

Android

手把手讲解 Android Hook-Activity的启动流程

發(fā)布時間:2023/12/15 Android 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 手把手讲解 Android Hook-Activity的启动流程 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

前言

手把手講解系列文章,是我寫給各位看官,也是寫給我自己的。
文章可能過分詳細,但是這是為了幫助到盡量多的人,畢竟工作5,6年,不能老吸血,也到了回饋開源的時候.
這個系列的文章:
1、用通俗易懂的講解方式,講解一門技術(shù)的實用價值
2、詳細書寫源碼的追蹤,源碼截圖,繪制類的結(jié)構(gòu)圖,盡量詳細地解釋原理的探索過程
3、提供Github 的 可運行的Demo工程,但是我所提供代碼,更多是提供思路,拋磚引玉,請酌情cv
4、集合整理原理探索過程中的一些坑,或者demo的運行過程中的注意事項
5、用gif圖,最直觀地展示demo運行效果

如果覺得細節(jié)太細,直接跳過看結(jié)論即可。
本人能力有限,如若發(fā)現(xiàn)描述不當(dāng)之處,歡迎留言批評指正。

學(xué)到老活到老,路漫漫其修遠兮。與眾君共勉 !


引子

上一篇文章手把手講解 Android Hook入門Demo 中,用了一個最最簡單的案例 講解hook是個什么玩意. 咱不能老玩低端,來點復(fù)雜的吧。Activity的啟動流程,做安卓開發(fā)的人都是繞不開它的,但是要真正知悉其源碼邏輯,還是不太容易.
先給出本文的代碼Demo,有興趣的大神們可以下載看看


鳴謝

翻了很多關(guān)于hook Activity啟動流程的博客,這位大佬的文章給我的啟發(fā)最大
https://blog.csdn.net/gdutxiaoxu/article/details/81459910
但是,可能大佬的博文對于有些基礎(chǔ)不足的初中級安卓工程師還不夠友好,所以我把大佬的思想用更通俗,更具象化的方式再展示一遍.并且,閱讀源碼的時候一些坑,我都會詳細給出解決方案。


正文大綱

1. 兩種啟動Activity的方式源碼追蹤 示例代碼,程序執(zhí)行走向圖.
2. 第一種啟動方式的hook方案
3. 第二種啟動方式的hook方案
4. 目前方案弊端分析
5. 最終解決方案
6. HOOK開發(fā)可能的坑


正文

1. 兩種啟動Activity的方式源碼追蹤 (源碼基于 SDK 28 ~ android-9.0)

方式1:使用Activity自帶的startActivity

示例代碼

?

private void startActivityByActivity() {Intent i = new Intent(MainActivity.this, Main2Activity.class);startActivity(i);}

程序執(zhí)行走向圖.

代碼追蹤:

?

image.png

?

image.png

?

image.png

這里有個if(mParent==null)判定,先看true分支:

發(fā)現(xiàn)一個坑,mInstrumentation.execStartActivity 這里居然不能繼續(xù)往下索引了?很奇怪,不過不重要,我們直接進入Instrumentation.java去找這個方法:

?

image.png


在這個execStartActivity中,可以找到關(guān)鍵代碼:

?

int result = ActivityManager.getService().startActivity(whoThread, who.getBasePackageName(), intent,intent.resolveTypeIfNeeded(who.getContentResolver()),token, target != null ? target.mEmbeddedID : null,requestCode, 0, null, options); checkStartActivityResult(result, intent);

通過這種方式啟動Activity,最終的執(zhí)行權(quán)被交給了 ActivityManager.getService()(即AMS),它的作用是 啟動一個Activity并且返回result,然后checkStartActivityResult(result, intent);這句話,對當(dāng)前的跳轉(zhuǎn)意圖intent進行檢測;

image.png

have you declared this activity in your AndroidManifest.xml 這句異常應(yīng)該很熟悉了吧?啟動一個沒有注冊的Activity的報錯.

再看個if(mParent==null)的false分支:

?

image.png

?

image.png


控制權(quán)依然是交給了mInstrumentation.execStartActivity(),剩余的代碼索引和上面的一樣.

?

所以,代碼索引的結(jié)論,按照一張圖來表示就是:

代碼索引結(jié)論圖1.png


方式2:使用applictonContext的startActivity

?

private void startActivityByApplicationContext() {Intent i = new Intent(MainActivity.this, Main2Activity.class);i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);getApplicationContext().startActivity(i);}

方式1 中已經(jīng)展示了源碼索引的方式,所以這里不再贅述貼圖.直接給出代碼索引結(jié)論圖:

代碼索引結(jié)論圖2.png

?

兩張圖對比,我們很容易得出一個結(jié)論:
啟動Activity的最終執(zhí)行權(quán),都被交給了 Instrumentation.java 類,
方式1:Activity.startActivity的最終執(zhí)行者是 它的mInstrumentation成員,mInstrumentation的持有者是 Activity自身.
方式2:getApplicationContext().startActivity(i); 的最終執(zhí)行者是:ActivityThread的 mInstrumentation成員,持有者是ActivityThread 主線程.
兩種方式都可以把mInstrumentation當(dāng)作hook切入點,將它從它的持有者中"偷梁換柱".

下面開始動手嘗試:


2. 第一種啟動方式的hook方案

創(chuàng)建一個HookActivityHelper.java ,然后三步走:

  • 找到hook點,以及hook對象的持有者,上文中已經(jīng)說明:hook點是Activity的mInstrumentation成員,持有者就是Activity
  • Field mInstrumentationField = Activity.class.getDeclaredField("mInstrumentation");mInstrumentationField.setAccessible(true);Instrumentation base = (Instrumentation) mInstrumentationField.get(activity);

    base是系統(tǒng)原來的執(zhí)行邏輯,存起來后面用得著.

  • 創(chuàng)建Instrumentation代理類, 繼承Instrumentation然后,重寫execStartActivity方法,加入自己的邏輯,然后再執(zhí)行系統(tǒng)的邏輯.
  • ?

    private static class ProxyInstrumentation extends Instrumentation {public ProxyInstrumentation(Instrumentation base) {this.base = base;}Instrumentation base;public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {Log.d("ProxyInstrumentation", "我們自己的邏輯");//這里還要執(zhí)行系統(tǒng)的原本邏輯,但是突然發(fā)現(xiàn),這個execStartActivity居然是hide的,只能反射咯try {Class<?> InstrumentationClz = Class.forName("android.app.Instrumentation");Method execStartActivity = InstrumentationClz.getDeclaredMethod("execStartActivity",Context.class, IBinder.class, IBinder.class, Activity.class,Intent.class, int.class, Bundle.class);return (ActivityResult) execStartActivity.invoke(base, who, contextThread, token, target, intent, requestCode, options);} catch (Exception e) {e.printStackTrace();}return null;}}
  • 用代理類對象替換 hook對象.
  • ?

    ProxyInstrumentation proxyInstrumentation = new ProxyInstrumentation(base);mInstrumentationField.set(activity, proxyInstrumentation);

    如何使用: 在MainActivity的onCreate中加入一行ActivityHookHelper.hook(this)

    ?

    public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ActivityHookHelper.hook(this);findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {startActivityByActivity();}});}private void startActivityByActivity() {Intent i = new Intent(MainActivity.this, Main2Activity.class);startActivity(i);}}

    效果:跳轉(zhuǎn)依然正常,并且logcat中可以發(fā)現(xiàn)下面的日志.

    image.png

    ok,插入自己的邏輯,成功


    3. 第二種啟動方式的hook方案

    創(chuàng)建ApplicationContextHookHelper.java,然后 同樣是三步走:

    1.確定hook的對象和該對象的持有者
    鎖定 ActivityThread的mInstrumentation成員.

    ?

    //1.主線程ActivityThread內(nèi)部的mInstrumentation對象,先把他拿出來Class<?> ActivityThreadClz = Class.forName("android.app.ActivityThread");//再拿到sCurrentActivityThreadField sCurrentActivityThreadField = ActivityThreadClz.getDeclaredField("sCurrentActivityThread");sCurrentActivityThreadField.setAccessible(true);Object activityThreadObj = sCurrentActivityThreadField.get(null);//靜態(tài)變量的屬性get不需要參數(shù),傳null即可.//再去拿它的mInstrumentationField mInstrumentationField = ActivityThreadClz.getDeclaredField("mInstrumentation");mInstrumentationField.setAccessible(true);Instrumentation base = (Instrumentation) mInstrumentationField.get(activityThreadObj);// OK,拿到

    2.創(chuàng)建代理對象 和上面的代理類一模一樣,就不重復(fù)貼代碼了

    ?

    //2.構(gòu)建自己的代理對象,這里Instrumentation是一個class,而不是接口,所以只能用創(chuàng)建內(nèi)部類的方式來做ProxyInstrumentation proxyInstrumentation = new ProxyInstrumentation(base);

    3.替換掉原對象

    ?

    //3.偷梁換柱mInstrumentationField.set(activityThreadObj, proxyInstrumentation);

    如何使用: 在Main4Activity的onCreate中加入一行ApplicationContextHookHelper.hook();

    ?

    public class Main4Activity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main4);ApplicationContextHookHelper.hook();findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {startActivityByApplicationContext();}});}private void startActivityByApplicationContext() {Intent i = new Intent(Main4Activity.this, Main5Activity.class);i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);getApplicationContext().startActivity(i);} }

    效果

    image.png

    ?

    OK,第二種啟動方式,我們也可以加入自己的邏輯了.hook成功!


    4. 目前方案弊端分析

    啟動方式1的hook: 只是在針對單個Activity類,來進行hook,多個Activity則需要寫多次,或者寫在BaseActivity里面.
    啟動方式2的hook:可以針對全局進行hook,無論多少個Activity,只需要調(diào)用一次ApplicationContextHookHelper.hook();函數(shù)即可,但是,它只能針對 getApplicationContext().startActivity(i); 普通的Activity.startActivity則不能起作用.

    那么有沒有一種完全體的解決方案:能夠在全局起作用,并且可以在兩種啟動方式下都能hook.
    回顧之前的兩張代碼索引結(jié)論圖,會發(fā)現(xiàn),兩種啟動Activity的方式,最終都被執(zhí)行到了 AMS內(nèi)部,
    下一步,嘗試hook AMS.


    5. 最終解決方案

    代碼索引: 基于SDK 28 ~ android9.0

    下方紅框標(biāo)記的部分,就是取得AMS(ActivityManagerService實例)的代碼.

    image.png


    如果可以在系統(tǒng)接收到AMS實例之前,把他截了,是不是就可以達到我們的目的?
    進去看看getService的代碼:

    ?

    image.png

    真正的AMS實例來自一個Singleton單例輔助類的create()方法,并且這個Singleton單例類,提供get方法,獲得真正的實例.

    image.png

    那么,我們從這個單例中,就可以獲得系統(tǒng)當(dāng)前的 AMS實例,將它取出來,然后保存.
    OK,確認:
    hook對象: ActivityManager的IActivityManagerSingleton成員 變量內(nèi)的 單例 mInstance.
    hook對象的持有者:ActivityManager的IActivityManagerSingleton成員變量

    那么,動手:

  • 找到hook對象,并且存起來
  • ?

    //1.把hook的對象取出來保存//矮油,靜態(tài)的耶,開心.Class<?> ActivityManagerClz = Class.forName("android.app.ActivityManager");Method getServiceMethod = ActivityManagerClz.getDeclaredMethod("getService");final Object IActivityManagerObj = getServiceMethod.invoke(null);//OK,已經(jīng)取得這個系統(tǒng)自己的AMS實例
  • 創(chuàng)建自己的代理類對象,IActivityManager 是一個AIDL生成的動態(tài)接口類,所以在編譯時,androidStudio會找不到這個類,所以,先反射,然后用Proxy進行創(chuàng)建代理。
  • ?

    //2.現(xiàn)在創(chuàng)建我們的AMS實例//由于IActivityManager是一個接口,那么我們可以使用Proxy類來進行代理對象的創(chuàng)建// 結(jié)果被擺了一道,IActivityManager這玩意居然還是個AIDL,動態(tài)生成的類,編譯器還不認識這個類,怎么辦?反射咯Class<?> IActivityManagerClz = Class.forName("android.app.IActivityManager");Object proxyIActivityManager = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{IActivityManagerClz}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//proxy是創(chuàng)建出來的代理類,method是接口中的方法,args是接口執(zhí)行時的實參if (method.getName().equals("startActivity")) {Log.d("GlobalActivityHook", "全局hook 到了 startActivity");}return method.invoke(IActivityManagerObj, args);}});
  • 偷梁換柱:這次有點復(fù)雜, 不再是簡單的field.set,因為這次的hook對象被包裹在了一個Singleton里。
  • ?

    //3.偷梁換柱,這里有點糾結(jié),這個實例居然被藏在了一個單例輔助類里面Field IActivityManagerSingletonField = ActivityManagerClz.getDeclaredField("IActivityManagerSingleton");IActivityManagerSingletonField.setAccessible(true);Object IActivityManagerSingletonObj = IActivityManagerSingletonField.get(null);//反射創(chuàng)建一個Singleton的classClass<?> SingletonClz = Class.forName("android.util.Singleton");Field mInstanceField = SingletonClz.getDeclaredField("mInstance");mInstanceField.setAccessible(true);mInstanceField.set(IActivityManagerSingletonObj, proxyIActivityManager);

    使用方法:老樣子,在你自己的Activity onCreate里面加入GlobalActivityHookHelper.hook();
    運行起來,預(yù)期結(jié)果應(yīng)該是:能夠在logcat中看到日志 :
    GlobalActivityHook - 全局hook 到了 startActivity;
    但是,你運行起來可能看不到這一行。

    如果你看不到這個日志,那么原因就是:

    程序報錯了,

    ?

    報錯啦!


    沒有這樣的方法,怎么回事?
    debug找原因:

    image.png


    為什么會沒有g(shù)etService這個方法!?
    查看了我當(dāng)前設(shè)備的系統(tǒng)版本號

    image.png


    居然是23版本,6.0.
    所以,恍然大悟,我們寫的hook代碼并沒有兼容性,遇到低版本的設(shè)備,就失靈了.

    ?

    解決方案:

    1.找到SDK 23的源碼
    (注意,前方有坑,androidStudio,你如果直接把combileSDK改成23.會出現(xiàn)很多位置問題,所以不建議這么做. 但是我們一定要看SDK 23的源碼,怎么辦?

    • .在線查看源碼 - https://www.androidos.net.cn/sourcecode ;
    • 從谷歌官網(wǎng)下載SDK 23的源碼,然后用SourceInsight查看)

    2.查看getService方法不存在的原因,兩個版本28 和 23,在這一塊代碼上有什么不同.
    3.改造 GlobalActivityHookHelper.java ,判定當(dāng)前設(shè)備的系統(tǒng)版本號,讓它可以兼容所有版本.

    按照上面的步驟:
    我發(fā)現(xiàn)SDK 23里面:
    Instrumentation類的 execStartActivitiesAsUser(Context who, IBinder contextThread, IBinder token, Activity target, Intent[] intents, Bundle options, int userId)方法里,獲取AMS實例的方式完全不同.

    ?

    image.png


    它是使用 ActivityManagerNative.getDefault()來獲得的,繼續(xù)往下找,看看有沒有什么不同。
    進去ActivityManagerNative 找找看:

    image.png

    ?

    image.png

    ?

    OK,找到了區(qū)別,確定結(jié)論:SDK 28和23在這塊代碼上的區(qū)別就是:
    獲得AMS實例的類名和方法名都不同.另外,查了度娘之后發(fā)現(xiàn),這個變化是在SDK 26版本修改的,所以26和26以后,ActivityManager.getService()來獲取,26以前,用ActivityManagerNative.getDefault()來獲得
    調(diào)整當(dāng)前的hook方法,修改為下面這樣:

    ?

    public class GlobalActivityHookHelper {//設(shè)備系統(tǒng)版本是不是大于等于26private static boolean ifSdkOverIncluding26() {int SDK_INT = Build.VERSION.SDK_INT;if (SDK_INT > 26 || SDK_INT == 26) {return true;} else {return false;}}public static void hook() {try {Class<?> ActivityManagerClz;final Object IActivityManagerObj;if (ifSdkOverIncluding26()) {ActivityManagerClz = Class.forName("android.app.ActivityManager");Method getServiceMethod = ActivityManagerClz.getDeclaredMethod("getService");IActivityManagerObj = getServiceMethod.invoke(null);//OK,已經(jīng)取得這個系統(tǒng)自己的AMS實例} else {ActivityManagerClz = Class.forName("android.app.ActivityManagerNative");Method getServiceMethod = ActivityManagerClz.getDeclaredMethod("getDefault");IActivityManagerObj = getServiceMethod.invoke(null);//OK,已經(jīng)取得這個系統(tǒng)自己的AMS實例}//2.現(xiàn)在創(chuàng)建我們的AMS實例//由于IActivityManager是一個接口,那么其實我們可以使用Proxy類來進行代理對象的創(chuàng)建// 結(jié)果被擺了一道,IActivityManager這玩意居然還是個AIDL,動態(tài)生成的類,編譯器還不認識這個類,怎么辦?反射咯Class<?> IActivityManagerClz = Class.forName("android.app.IActivityManager");Object proxyIActivityManager = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{IActivityManagerClz}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//proxy是創(chuàng)建出來的代理類,method是接口中的方法,args是接口執(zhí)行時的實參if (method.getName().equals("startActivity")) {Log.d("GlobalActivityHook", "全局hook 到了 startActivity");}return method.invoke(IActivityManagerObj, args);}});//3.偷梁換柱,這里有點糾結(jié),這個實例居然被藏在了一個單例輔助類里面Field IActivityManagerSingletonField;if (ifSdkOverIncluding26()) {IActivityManagerSingletonField = ActivityManagerClz.getDeclaredField("IActivityManagerSingleton");} else {IActivityManagerSingletonField = ActivityManagerClz.getDeclaredField("gDefault");}IActivityManagerSingletonField.setAccessible(true);Object IActivityManagerSingletonObj = IActivityManagerSingletonField.get(null);Class<?> SingletonClz = Class.forName("android.util.Singleton");//反射創(chuàng)建一個Singleton的classField mInstanceField = SingletonClz.getDeclaredField("mInstance");mInstanceField.setAccessible(true);mInstanceField.set(IActivityManagerSingletonObj, proxyIActivityManager);} catch (Exception e) {e.printStackTrace();}} }

    再次嘗試:

    image.png

    成功,實現(xiàn)了全局范圍內(nèi)的startActivity動作的hook.


    6. HOOK開發(fā)可能的坑

    1. androidStudio閱讀源碼很多類無法索引,這是因為有一些類是@hide的,無法Ctrl點進去,
    解決方案:Ctrl+shift+R 輸入類名,手動進入.

    2.androidStudio閱讀源碼直接報紅 :或者一些是AIDL動態(tài)生成的接口,無法直接查看,比IActivityManager. ,
    解決方案:這種接口不用管它,如果非要用到它,那就使用本類的包名+IActivityManager作為全限定名,去反射創(chuàng)建它.

    3. hook開發(fā),是學(xué)習(xí)源碼思想,改變源碼執(zhí)行流程,所以,在多個版本的設(shè)備上運行,很容易發(fā)生不兼容的情況.
    解決方案:找到不兼容的設(shè)備版本,根據(jù)報的異常,參照源碼的版本變遷做出相應(yīng)的兼容性改動.


    結(jié)語

    歷時3天,忙里偷閑,總算是寫完了.
    喜歡的客官幫忙點個贊哦,你們的鼓勵是我最大的動力,以后還會更新更多干貨.
    最后~本文的代碼Demo奉上.



    作者:波瀾步驚
    鏈接:https://www.jianshu.com/p/efce746836f5
    來源:簡書
    著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。

    總結(jié)

    以上是生活随笔為你收集整理的手把手讲解 Android Hook-Activity的启动流程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。