溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Android Hook 機(jī)制之實(shí)戰(zhàn)模擬

發(fā)布時間:2020-05-22 11:42:06 來源:網(wǎng)絡(luò) 閱讀:652 作者:Android解析 欄目:移動開發(fā)

簡介

什么是 Hook

Hook 又叫“鉤子”,它可以在事件傳送的過程中截獲并監(jiān)控事件的傳輸,將自身的代碼與系統(tǒng)方法進(jìn)行融入。這樣當(dāng)這些方法被調(diào)用時,也就可以執(zhí)行我們自己的代碼,這也是面向切面編程的思想(AOP)。

Hook 分類

1.根據(jù)Android開發(fā)模式,Native模式(C/C++)和Java模式(Java)區(qū)分,在Android平臺上

  • Java層級的Hook;

  • Native層級的Hook;

2.根 Hook 對象與 Hook 后處理事件方式不同,Hook還分為:

  • 消息Hook;

  • API Hook;

3.針對Hook的不同進(jìn)程上來說,還可以分為:

  • 全局Hook;

  • 單個進(jìn)程Hook;

常見 Hook 框架

在Android開發(fā)中,有以下常見的一些Hook框架:

  • Xposed

    通過替換 /system/bin/app_process 程序控制 Zygote 進(jìn)程,使得 app_process 在啟動過程中會加載 XposedBridge.jar 這個 Jar 包,從而完成對 Zygote 進(jìn)程及其創(chuàng)建的 Dalvik 虛擬機(jī)的劫持。
    Xposed 在開機(jī)的時候完成對所有的 Hook Function 的劫持,在原 Function 執(zhí)行的前后加上自定義代碼。

  • Cydia Substrate

    Cydia Substrate 框架為蘋果用戶提供了越獄相關(guān)的服務(wù)框架,當(dāng)然也推出了 Android 版 。Cydia Substrate 是一個代碼修改平臺,它可以修改任何進(jìn)程的代碼。不管是用 Java 還是 C/C++(native代碼)編寫的,而 Xposed 只支持 Hook app_process 中的 Java 函數(shù)。

  • Legend

    Legend 是 Android 免 Root 環(huán)境下的一個 Apk Hook 框架,該框架代碼設(shè)計(jì)簡潔,通用性高,適合逆向工程時一些 Hook 場景。大部分的功能都放到了 Java 層,這樣的兼容性就非常好。
    原理是這樣的,直接構(gòu)造出新舊方法對應(yīng)的虛擬機(jī)數(shù)據(jù)結(jié)構(gòu),然后替換信息寫到內(nèi)存中即可。

Hook 必須掌握的知識

  • 反射

如果你對反射還不是很熟悉的話,建議你先復(fù)習(xí)一下 java 反射的相關(guān)知識。

動態(tài)代理是指在運(yùn)行時動態(tài)生成代理類,不需要我們像靜態(tài)代理那個去手動寫一個個的代理類。在 java 中,我們可以使用 InvocationHandler 實(shí)現(xiàn)動態(tài)代理。

本文的主要內(nèi)容是講解單個進(jìn)程的 Hook,以及怎樣 Hook。


Hook 使用實(shí)例

Hook 選擇的關(guān)鍵點(diǎn)

  • Hook 的選擇點(diǎn):盡量靜態(tài)變量和單例,因?yàn)橐坏﹦?chuàng)建對象,它們不容易變化,非常容易定位。

  • Hook 過程:

    • 尋找 Hook 點(diǎn),原則是盡量靜態(tài)變量或者單例對象,盡量 Hook public 的對象和方法。

    • 選擇合適的代理方式,如果是接口可以用動態(tài)代理。

    • 偷梁換柱——用代理對象替換原始對象。

  • Android 的 API 版本比較多,方法和類可能不一樣,所以要做好 API 的兼容工作。

簡單案例一: 使用 Hook 修改 View.OnClickListener 事件

首先,我們先分析 View.setOnClickListener 源碼,找出合適的 Hook 點(diǎn)??梢钥吹?OnClickListener 對象被保存在了一個叫做 ListenerInfo 的內(nèi)部類里,其中 mListenerInfo 是 View 的成員變量。ListeneInfo 里面保存了 View 的各種監(jiān)聽事件。因此,我們可以想辦法 hook ListenerInfo 的 mOnClickListener 。

public?void?setOnClickListener(@Nullable?OnClickListener?l)?{????if?(!isClickable())?{
????????setClickable(true);
????}
????getListenerInfo().mOnClickListener?=?l;
}static?class?ListenerInfo?{

?????---????ListenerInfo?getListenerInfo()?{????????if?(mListenerInfo?!=?null)?{????????????return?mListenerInfo;
????????}
????????mListenerInfo?=?new?ListenerInfo();????????return?mListenerInfo;
????}
????
????---
}

接下來,讓我們一起來看一下怎樣 Hook View.OnClickListener 事件?

大概分為三步:

  • 第一步:獲取 ListenerInfo 對象

從 View 的源代碼,我們可以知道我們可以通過 getListenerInfo 方法獲取,于是,我們利用反射得到 ListenerInfo 對象

  • 第二步:獲取原始的 OnClickListener事件方法

從上面的分析,我們知道 OnClickListener 事件被保存在 ListenerInfo 里面,同理我們利用反射獲取

  • 第三步:偷梁換柱,用 Hook代理類 替換原始的 OnClickListener

public?static?void?hookOnClickListener(View?view)?throws?Exception?{????//?第一步:反射得到?ListenerInfo?對象
????Method?getListenerInfo?=?View.class.getDeclaredMethod("getListenerInfo");
????getListenerInfo.setAccessible(true);
????Object?listenerInfo?=?getListenerInfo.invoke(view);????//?第二步:得到原始的?OnClickListener事件方法
????Class<?>?listenerInfoClz?=?Class.forName("android.view.View$ListenerInfo");
????Field?mOnClickListener?=?listenerInfoClz.getDeclaredField("mOnClickListener");
????mOnClickListener.setAccessible(true);
????View.OnClickListener?originOnClickListener?=?(View.OnClickListener)?mOnClickListener.get(listenerInfo);????//?第三步:用?Hook代理類?替換原始的?OnClickListener
????View.OnClickListener?hookedOnClickListener?=?new?HookedClickListenerProxy(originOnClickListener);
????mOnClickListener.set(listenerInfo,?hookedOnClickListener);
}
public?class?HookedClickListenerProxy?implements?View.OnClickListener?{????private?View.OnClickListener?origin;????public?HookedClickListenerProxy(View.OnClickListener?origin)?{????????this.origin?=?origin;
????}????@Override
????public?void?onClick(View?v)?{
????????Toast.makeText(v.getContext(),?"Hook?Click?Listener",?Toast.LENGTH_SHORT).show();????????if?(origin?!=?null)?{
????????????origin.onClick(v);
????????}
????}
????
}

執(zhí)行以下代碼,將會看到當(dāng)我們點(diǎn)擊該按鈕的時候,會彈出 toast “Hook Click Listener”

mBtn1?=?(Button)?findViewById(R.id.btn_1);
mBtn1.setOnClickListener(this);try?{
????HookHelper.hookOnClickListener(mBtn1);
}?catch?(Exception?e)?{
????e.printStackTrace();
}

簡單案例二: HooK Notification

發(fā)送消息到通知欄的核心代碼如下:

NotificationManager?notificationManager?=?(NotificationManager)?context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(id,?builder.build());

跟蹤 notify 方法發(fā)現(xiàn)最終會調(diào)用到 notifyAsUser 方法

public?void?notify(String?tag,?int?id,?Notification?notification){
????notifyAsUser(tag,?id,?notification,?new?UserHandle(UserHandle.myUserId()));
}

而在 notifyAsUser 方法中,我們驚喜地發(fā)現(xiàn) service 是一個單例,因此,我們可以想方法 hook 住這個 service,而 notifyAsUser 最終會調(diào)用到 service 的 enqueueNotificationWithTag 方法。因此 hook 住 service 的 enqueueNotificationWithTag 方法即可

public?void?notifyAsUser(String?tag,?int?id,?Notification?notification,?UserHandle?user){????//?
????INotificationManager?service?=?getService();
????String?pkg?=?mContext.getPackageName();????//?Fix?the?notification?as?best?we?can.
????Notification.addFieldsFromContext(mContext,?notification);????if?(notification.sound?!=?null)?{
????????notification.sound?=?notification.sound.getCanonicalUri();????????if?(StrictMode.vmFileUriExposureEnabled())?{
????????????notification.sound.checkFileUriExposed("Notification.sound");
????????}
????}
????fixLegacySmallIcon(notification,?pkg);????if?(mContext.getApplicationInfo().targetSdkVersion?>?Build.VERSION_CODES.LOLLIPOP_MR1)?{????????if?(notification.getSmallIcon()?==?null)?{????????????throw?new?IllegalArgumentException("Invalid?notification?(no?valid?small?icon):?"
????????????????????+?notification);
????????}
????}????if?(localLOGV)?Log.v(TAG,?pkg?+?":?notify("?+?id?+?",?"?+?notification?+?")");????final?Notification?copy?=?Builder.maybeCloneStrippedForDelivery(notification);????try?{
????????service.enqueueNotificationWithTag(pkg,?mContext.getOpPackageName(),?tag,?id,
????????????????copy,?user.getIdentifier());
????}?catch?(RemoteException?e)?{????????throw?e.rethrowFromSystemServer();
????}
}private?static?INotificationManager?sService;static?public?INotificationManager?getService(){????if?(sService?!=?null)?{????????return?sService;
????}
????IBinder?b?=?ServiceManager.getService("notification");
????sService?=?INotificationManager.Stub.asInterface(b);????return?sService;
}

綜上,要 Hook ?Notification,大概需要三步:

  • 第一步:得到 NotificationManager 的 sService

  • 第二步:因?yàn)?sService 是接口,所以我們可以使用動態(tài)代理,獲取動態(tài)代理對象

  • 第三步:偷梁換柱,使用動態(tài)代理對象 proxyNotiMng 替換系統(tǒng)的 sService

于是,我們可以寫出如下的代碼

public?static?void?hookNotificationManager(final?Context?context)?throws?Exception?{
????NotificationManager?notificationManager?=?(NotificationManager)?context.getSystemService(Context.NOTIFICATION_SERVICE);

????Method?getService?=?NotificationManager.class.getDeclaredMethod("getService");
????getService.setAccessible(true);????//?第一步:得到系統(tǒng)的?sService
????final?Object?sOriginService?=?getService.invoke(notificationManager);

????Class?iNotiMngClz?=?Class.forName("android.app.INotificationManager");????//?第二步:得到我們的動態(tài)代理對象
????Object?proxyNotiMng?=?Proxy.newProxyInstance(context.getClass().getClassLoader(),?new
????????????Class[]{iNotiMngClz},?new?InvocationHandler()?{????????@Override
????????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{
????????????Log.d(TAG,?"invoke().?method:"?+?method);
????????????String?name?=?method.getName();
????????????Log.d(TAG,?"invoke:?name="?+?name);????????????if?(args?!=?null?&&?args.length?>?0)?{????????????????for?(Object?arg?:?args)?{
????????????????????Log.d(TAG,?"invoke:?arg="?+?arg);
????????????????}
????????????}
????????????Toast.makeText(context.getApplicationContext(),?"檢測到有人發(fā)通知了",?Toast.LENGTH_SHORT).show();????????????//?操作交由?sOriginService?處理,不攔截通知
????????????return?method.invoke(sOriginService,?args);????????????//?攔截通知,什么也不做
????????????//????????????????????return?null;
????????????//?或者是根據(jù)通知的?Tag?和?ID?進(jìn)行篩選
????????}
????});????//?第三步:偷梁換柱,使用?proxyNotiMng?替換系統(tǒng)的?sService
????Field?sServiceField?=?NotificationManager.class.getDeclaredField("sService");
????sServiceField.setAccessible(true);
????sServiceField.set(notificationManager,?proxyNotiMng);

}

Hook 使用進(jìn)階

Hook ClipboardManager

第一種方法

從上面的 hook NotificationManager 例子中,我們可以得知 NotificationManager 中有一個靜態(tài)變量 sService,這個變量是遠(yuǎn)端的 service。因此,我們嘗試查找 ClipboardManager 中是不是也存在相同的類似靜態(tài)變量。

查看它的源碼發(fā)現(xiàn)它存在 mService 變量,該變量是在 ClipboardManager 構(gòu)造函數(shù)中初始化的,而 ClipboardManager 的構(gòu)造方法用 @hide 標(biāo)記,表明該方法對調(diào)用者不可見。

而我們知道 ClipboardManager,NotificationManager 其實(shí)這些都是單例的,即系統(tǒng)只會創(chuàng)建一次。因此我們也可以認(rèn)為
ClipboardManager 的 mService 是單例的。因此 mService 應(yīng)該是可以考慮 hook 的一個點(diǎn)。

public?class?ClipboardManager?extends?android.text.ClipboardManager?{????private?final?Context?mContext;????private?final?IClipboard?mService;????/**?{@hide}?*/
????public?ClipboardManager(Context?context,?Handler?handler)?throws?ServiceNotFoundException?{
????????mContext?=?context;
????????mService?=?IClipboard.Stub.asInterface(
????????????????ServiceManager.getServiceOrThrow(Context.CLIPBOARD_SERVICE));
????}
}

接下來,我們再來一個看一下 ClipboardManager 的相關(guān)方法 setPrimaryClip , getPrimaryClip

public?void?setPrimaryClip(ClipData?clip)?{????try?{????????if?(clip?!=?null)?{
????????????clip.prepareToLeaveProcess(true);
????????}
????????mService.setPrimaryClip(clip,?mContext.getOpPackageName());
????}?catch?(RemoteException?e)?{????????throw?e.rethrowFromSystemServer();
????}
}/**
?*?Returns?the?current?primary?clip?on?the?clipboard.
?*/public?ClipData?getPrimaryClip()?{????try?{????????return?mService.getPrimaryClip(mContext.getOpPackageName());
????}?catch?(RemoteException?e)?{????????throw?e.rethrowFromSystemServer();
????}
}

可以發(fā)現(xiàn)這些方法最終都會調(diào)用到 mService 的相關(guān)方法。因此,ClipboardManager 的 mService 確實(shí)是一個可以 hook 的一個點(diǎn)。

hook ClipboardManager.mService ?的實(shí)現(xiàn)

大概需要三個步驟

  • 第一步:得到 ClipboardManager 的 mService

  • 第二步:初始化動態(tài)代理對象

  • 第三步:偷梁換柱,使用 proxyNotiMng 替換系統(tǒng)的 mService

public?static?void?hookClipboardService(final?Context?context)?throws?Exception?{
????ClipboardManager?clipboardManager?=?(ClipboardManager)?context.getSystemService(Context.CLIPBOARD_SERVICE);
????Field?mServiceFiled?=?ClipboardManager.class.getDeclaredField("mService");
????mServiceFiled.setAccessible(true);????//?第一步:得到系統(tǒng)的?mService
????final?Object?mService?=?mServiceFiled.get(clipboardManager);????
????//?第二步:初始化動態(tài)代理對象
????Class?aClass?=?Class.forName("android.content.IClipboard");
????Object?proxyInstance?=?Proxy.newProxyInstance(context.getClass().getClassLoader(),?new
????????????Class[]{aClass},?new?InvocationHandler()?{????????@Override
????????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{
????????????Log.d(TAG,?"invoke().?method:"?+?method);
????????????String?name?=?method.getName();????????????if?(args?!=?null?&&?args.length?>?0)?{????????????????for?(Object?arg?:?args)?{
????????????????????Log.d(TAG,?"invoke:?arg="?+?arg);
????????????????}
????????????}????????????if?("setPrimaryClip".equals(name))?{
????????????????Object?arg?=?args[0];????????????????if?(arg?instanceof?ClipData)?{
????????????????????ClipData?clipData?=?(ClipData)?arg;????????????????????int?itemCount?=?clipData.getItemCount();????????????????????for?(int?i?=?0;?i?<?itemCount;?i++)?{
????????????????????????ClipData.Item?item?=?clipData.getItemAt(i);
????????????????????????Log.i(TAG,?"invoke:?item="?+?item);
????????????????????}
????????????????}
????????????????Toast.makeText(context,?"檢測到有人設(shè)置粘貼板內(nèi)容",?Toast.LENGTH_SHORT).show();
????????????}?else?if?("getPrimaryClip".equals(name))?{
????????????????Toast.makeText(context,?"檢測到有人要獲取粘貼板的內(nèi)容",?Toast.LENGTH_SHORT).show();
????????????}????????????//?操作交由?sOriginService?處理,不攔截通知
????????????return?method.invoke(mService,?args);

????????}
????});????//?第三步:偷梁換柱,使用?proxyNotiMng?替換系統(tǒng)的?mService
????Field?sServiceField?=?ClipboardManager.class.getDeclaredField("mService");
????sServiceField.setAccessible(true);
????sServiceField.set(clipboardManager,?proxyInstance);

}

Android Hook 機(jī)制之實(shí)戰(zhàn)模擬

image

第二種方法

對 Android 源碼有基本了解的人都知道,Android 中的各種 Manager 都是通過 ServiceManager 獲取的。因此,我們可以通過 ServiceManager hook 所有系統(tǒng) Manager,ClipboardManager 當(dāng)然也不例外。

public?final?class?ServiceManager?{????/**
?????*?Returns?a?reference?to?a?service?with?the?given?name.
?????*?
?????*?@param?name?the?name?of?the?service?to?get
?????*?@return?a?reference?to?the?service,?or?<code>null</code>?if?the?service?doesn't?exist
?????*/
????public?static?IBinder?getService(String?name)?{????????try?{
????????????IBinder?service?=?sCache.get(name);????????????if?(service?!=?null)?{????????????????return?service;
????????????}?else?{????????????????return?getIServiceManager().getService(name);
????????????}
????????}?catch?(RemoteException?e)?{
????????????Log.e(TAG,?"error?in?getService",?e);
????????}????????return?null;
????}
}

老套路

  • 第一步:通過反射獲取剪切板服務(wù)的遠(yuǎn)程Binder對象,這里我們可以通過 ServiceManager getService 方法獲得

  • 第二步:創(chuàng)建我們的動態(tài)代理對象,動態(tài)代理原來的Binder對象

  • 第三步:偷梁換柱,把我們的動態(tài)代理對象設(shè)置進(jìn)去

public?static?void?hookClipboardService()?throws?Exception?{????//通過反射獲取剪切板服務(wù)的遠(yuǎn)程Binder對象
????Class?serviceManager?=?Class.forName("android.os.ServiceManager");
????Method?getServiceMethod?=?serviceManager.getMethod("getService",?String.class);
????IBinder?remoteBinder?=?(IBinder)?getServiceMethod.invoke(null,?Context.CLIPBOARD_SERVICE);????//新建一個我們需要的Binder,動態(tài)代理原來的Binder對象
????IBinder?hookBinder?=?(IBinder)?Proxy.newProxyInstance(serviceManager.getClassLoader(),????????????new?Class[]{IBinder.class},?new?ClipboardHookRemoteBinderHandler(remoteBinder));????//通過反射獲取ServiceManger存儲Binder對象的緩存集合,把我們新建的代理Binder放進(jìn)緩存
????Field?sCacheField?=?serviceManager.getDeclaredField("sCache");
????sCacheField.setAccessible(true);????Map<String,?IBinder>?sCache?=?(Map<String,?IBinder>)?sCacheField.get(null);
????sCache.put(Context.CLIPBOARD_SERVICE,?hookBinder);

}
public?class?ClipboardHookRemoteBinderHandler?implements?InvocationHandler?{????private?IBinder?remoteBinder;????private?Class?iInterface;????private?Class?stubClass;????public?ClipboardHookRemoteBinderHandler(IBinder?remoteBinder)?{????????this.remoteBinder?=?remoteBinder;????????try?{????????????this.iInterface?=?Class.forName("android.content.IClipboard");????????????this.stubClass?=?Class.forName("android.content.IClipboard$Stub");
????????}?catch?(Exception?e)?{
????????????e.printStackTrace();
????????}
????}????@Override
????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{
????????Log.d("RemoteBinderHandler",?method.getName()?+?"()?is?invoked");????????if?("queryLocalInterface".equals(method.getName()))?{????????????//這里不能攔截具體的服務(wù)的方法,因?yàn)檫@是一個遠(yuǎn)程的Binder,還沒有轉(zhuǎn)化為本地Binder對象
????????????//所以先攔截我們所知的queryLocalInterface方法,返回一個本地Binder對象的代理
????????????return?Proxy.newProxyInstance(remoteBinder.getClass().getClassLoader(),????????????????????new?Class[]{this.iInterface},????????????????????new?ClipboardHookLocalBinderHandler(remoteBinder,?stubClass));
????????}????????return?method.invoke(remoteBinder,?args);
????}
}


向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI